Skip to main content

OutputBudget

Struct OutputBudget 

Source
pub struct OutputBudget { /* private fields */ }
Expand description

A configurable ceiling on how much output a potentially large content operation may buffer before it is refused — a diff (diff_text/diff), a file’s bytes at a revision (show_file/file_show), a forge PR/MR diff (pr_diff), and the diagnostic (error/progress) output of clone/fetch.

This is the single, shared knob the CLI wrappers (vcs-git, vcs-jj, the forge crates) and the facades (vcs-core, vcs-forge, the MCP server) all use, so the limit is configured and reasoned about one way across the workspace instead of one ad-hoc cap per client. Set a per-client default with each client’s default_output_budget(...) builder (inherited by any facade built over that client); raise or lower it for a single call with the *_within method variants (diff_text_within, show_file_within, pr_diff_within, …). There is no un-overridable global constant — the default is unlimited (retain everything, the pre-budget behaviour), and every cap is a caller choice.

It projects onto two processkit OutputBufferPolicy shapes, so one budget drives both kinds of bounded output:

  • content_policy — a fail-loud ceiling (OverflowMode::Error): once the cap is reached the run errors with ErrorReason::OutputTooLarge, carrying the actual (total_lines/total_bytes) and allowed (max_lines/max_bytes) sizes — the reported total_bytes is in the same unit as the ceiling that fired, see bytes. The pipe is still drained (the child never blocks) and output past the ceiling is counted but never retained, so memory stays bounded and a truncated result is never handed back as if complete. This is what the content verbs use.
  • diagnostic_policy — a drop-oldest tail bound: caps the retained error/progress output of a discard verb (clone/fetch) without converting a real failure into OutputTooLarge, so transient-failure classification still reads the (tail-preserved) message. This is the same shape the gh run watch cap uses.

The byte ceiling (bytes) is the load-bearing memory bound: the content verbs capture raw stdout (no line splitting), where the byte cap — not the line cap — is what processkit enforces. A line ceiling (with_max_lines) is an optional extra that also bounds line-pumped output (a diagnostic stream, a verb’s stderr).

A streamed run (run_with_progress_within) is bounded by the same budget without going through an OutputBufferPolicy at all: its events carry every line whatever the command’s buffer does, so the drop-oldest tail is applied to the copy that function retains, in its own documented unit. That is what makes a streaming clone/fetch memory-bounded by the very knob that bounds its captured twin.

Each captured stream carries the ceiling independently: a content verb’s raw stdout and its line-pumped stderr each get their own max_bytes budget (so one call’s worst-case retained memory is about twice the cap, not the cap), and either one reaching it is what raises OutputTooLarge. What counts as a “byte” is not the same on the two streams — see bytes.

Implementations§

Source§

impl OutputBudget

Source

pub const fn unlimited() -> OutputBudget

No ceiling — retain everything (the default, and the pre-budget behaviour). content_policy / diagnostic_policy return None, leaving the command’s own (unbounded) buffer untouched.

Source

pub const fn bytes(max_bytes: usize) -> OutputBudget

A byte ceiling of max_bytes, in the unit OutputBufferPolicy::max_bytes caps. The primary, memory-bounding knob: it applies to the raw-stdout content path where a line cap would not. Add a line ceiling with with_max_lines.

§What a byte counts as

The unit differs by stream, because the two are captured differently. Both ceilings fire strictly past the cap: output sitting exactly on max_bytes is still accepted.

  • Raw stdout — what every content verb reads (ManagedClient::run_untrimmed: diff_text, show_file, pr_diff, template_query, …): the cap counts the bytes read from the pipe verbatim, with no line framing to strip and nothing decoded. This is what processkit has always counted on this path, so its 3.0 switch to raw-pipe-byte accounting did not move this ceiling — a 64 KiB content cap refuses exactly the same reads it did before.
  • Line-pumped stderr (and any line-captured stream): since processkit 3.0 the fail-loud ceiling counts the raw bytes read from the pipe — line terminators and invalid-UTF-8 bytes included — where it previously counted only the decoded line content. A plain LF stream therefore counts one byte per line more than it used to (the LF is now charged, not just a CRLF’s extra \r), so a cap set against the old unit trips marginally earlier on this stream.

ErrorReason::OutputTooLarge’s reported total_bytes is in the same unit as whichever of those ceilings fired.

The drop-oldest diagnostic_policy is a third case, unaffected by that change: what it retains is bounded by the decoded line-content bytes it holds, not by the raw bytes it saw. A streamed run’s local tail (run_with_progress_within) is a fourth: it charges the decoded line content plus the one \n joining each retained pair — the bytes of the string it actually hands back.

Source

pub const fn with_max_lines(self, max_lines: usize) -> OutputBudget

Add a line ceiling of max_lines (an extra bound on line-pumped output — diagnostics, a verb’s stderr). Composes with any bytes cap; whichever ceiling is reached first fires.

Source

pub const fn is_unlimited(&self) -> bool

Whether no ceiling is set (retain everything).

Source

pub const fn max_bytes(&self) -> Option<usize>

The configured byte ceiling, if any.

Source

pub const fn max_lines(&self) -> Option<usize>

The configured line ceiling, if any.

Source

pub fn content_policy(&self) -> Option<OutputBufferPolicy>

The fail-loud OutputBufferPolicy for a content verb — errors with ErrorReason::OutputTooLarge once the ceiling is reached, never retaining or returning a truncated tail. None when unlimited (leave the command’s default buffer).

Source

pub fn diagnostic_policy(&self) -> Option<OutputBufferPolicy>

The drop-oldest OutputBufferPolicy for a discard verb’s diagnostic output (clone/fetch): keeps the last max_bytes/max_lines (the tail, where a CLI’s fatal line sits) and flags truncation, but does not raise ErrorReason::OutputTooLarge — so a genuine failure still surfaces as ErrorReason::Exit and stays classifiable (is_transient_fetch_error, is_lock_contention). None when unlimited.

The retained tail is measured in decoded line-content bytes, which is processkit’s drop-mode accounting and is deliberately not the raw-pipe-byte unit its 3.0 release re-based the fail-loud OverflowMode::Error ceiling onto — so how much tail this projection keeps is unchanged by that release. Note also that a single line longer than max_bytes is never assembled by the pump and so is dropped whole (counted only as truncation): keep the byte cap comfortably above a plausible single fatal line.

Trait Implementations§

Source§

impl Clone for OutputBudget

Source§

fn clone(&self) -> OutputBudget

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for OutputBudget

Source§

impl Debug for OutputBudget

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for OutputBudget

Source§

fn default() -> OutputBudget

unlimited — the budget is opt-in.

Source§

impl Eq for OutputBudget

Source§

impl PartialEq for OutputBudget

Source§

fn eq(&self, other: &OutputBudget) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for OutputBudget

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Any for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Source§

fn type_name(&self) -> &'static str

Source§

impl<T> AnySync for T
where T: Any + Send + Sync,

Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Send + Sync>

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more