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 withErrorReason::OutputTooLarge, carrying the actual (total_lines/total_bytes) and allowed (max_lines/max_bytes) sizes — the reportedtotal_bytesis in the same unit as the ceiling that fired, seebytes. 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 intoOutputTooLarge, so transient-failure classification still reads the (tail-preserved) message. This is the same shape thegh run watchcap 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
impl OutputBudget
Sourcepub const fn unlimited() -> Self
pub const fn unlimited() -> Self
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.
Sourcepub const fn bytes(max_bytes: usize) -> Self
pub const fn bytes(max_bytes: usize) -> Self
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 whatprocesskithas 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.
Sourcepub const fn with_max_lines(self, max_lines: usize) -> Self
pub const fn with_max_lines(self, max_lines: usize) -> Self
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.
Sourcepub const fn is_unlimited(&self) -> bool
pub const fn is_unlimited(&self) -> bool
Whether no ceiling is set (retain everything).
Sourcepub fn content_policy(&self) -> Option<OutputBufferPolicy>
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).
Sourcepub fn diagnostic_policy(&self) -> Option<OutputBufferPolicy>
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
impl Clone for OutputBudget
Source§fn clone(&self) -> OutputBudget
fn clone(&self) -> OutputBudget
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more