#[non_exhaustive]pub enum Error {
Show 13 variants
Spawn {
program: String,
source: Error,
},
NotFound {
program: String,
searched: Option<String>,
},
CassetteMiss {
program: String,
},
Exit {
program: String,
code: i32,
stdout: String,
stderr: String,
},
Timeout {
program: String,
timeout: Duration,
stdout: String,
stderr: String,
},
OutputTooLarge {
program: String,
line_limit: Option<usize>,
byte_limit: Option<usize>,
total_lines: usize,
total_bytes: usize,
},
NotReady {
program: String,
timeout: Duration,
},
Parse {
program: String,
message: String,
},
Unsupported {
operation: String,
},
Cancelled {
program: String,
},
Signalled {
program: String,
signal: Option<i32>,
stdout: String,
stderr: String,
},
Stdin {
program: String,
source: Error,
},
Io(Error),
}Expand description
Errors produced when launching or running a child process.
Spawn failures, a non-zero exit (Exit), timeouts, and IO
errors fold into one structured enum, so callers can pattern-match on the
failure mode instead of parsing strings.
Debug is manual, not derived (L22): the Exit variant
carries both captured streams in full, and a derived Debug would dump them
— potentially multi-MiB — into a {e:?} log line or an .unwrap() panic
message. The manual impl bounds each stream to a 200-byte preview (mirroring
the Display tail cap) and redacts
NotFound’s
searched (the PATH env value) to a directory count, honoring the crate’s
“never log environment values” rule. The exact streams remain reachable via
the public fields.
Variants (Non-exhaustive)§
This enum is marked as non-exhaustive
Spawn
The child process could not be started (binary not found, permission denied, …).
NotFound
The program could not be located, so no child was ever started — it is
not installed, not on PATH, or the given path does not resolve to an
executable. The single representation of “program not found”: the
launch path routes every such failure here regardless of how the program
was named (bare name vs path) or platform (D11), so a caller matches one
variant and is_not_found classifies it.
Distinct from Spawn, which covers OS-level failures
once the program is located (permission denied, busy, a bad working
directory, a .cmd/.bat on Windows that needs cmd.exe, etc.) —
those are not is_not_found.
The searched cause is structured: Some(dirs) when a bare name was
looked up against PATH (the directories searched), None when the
program was given as a path or PATH was customized (no PATH search
applied, so there are no directories to name).
The Display message intentionally omits searched — PATH is an
environment value and must never appear in logs per the crate’s
security policy. Access searched directly for a diagnostic. The message
says “on PATH” only when a PATH search actually happened (searched is
Some); a path-form or customized-PATH program reads simply “not found”.
Fields
searched: Option<String>The PATH directories searched, joined by the platform separator
(: on Unix, ; on Windows) — Some for a bare-name PATH lookup
(empty string when PATH is unset), None when no PATH search
applied (a path-form program, or a customized PATH). Not included in
Display; use it directly when building a user-facing diagnostic.
CassetteMiss
A cassette replay found no recording matching the invocation — a
stale or incomplete cassette, not a missing program (F7). Kept distinct
from Spawn / NotFound so a wrapper
that treats “tool not installed” as an optional dependency does not
silently swallow a stale cassette as an absent tool.
is_not_found returns false for this variant.
Exit
The process ran to completion but exited with a non-zero status.
Produced by the ensure_success helpers; the raw exit code is otherwise
reported without erroring (a non-zero exit is not inherently a failure).
Both captured streams are carried in full: git/jj write decisive
diagnostics to stdout on failure (CONFLICT (content): …, nothing to commit, working tree clean), so a caller building a user-facing message
wants stdout as a fallback when stderr is empty — see
diagnostic. Consumers also classify on these fields
(grep for a marker, parse a sub-code), so they are never truncated before
the caller sees them; only the Display message below is bounded.
The one-line Display message appends the last non-empty line of
diagnostic, capped at 200 bytes — `git` exited with code 2: fatal: boom — actionable in a log line without dumping
multi-KiB streams into it.
Fields
Timeout
The process exceeded its configured timeout and was killed.
Carries whatever the run captured before the deadline killed it
(D12): a hung tool’s partial stderr is frequently the explanation
(waiting for lock held by pid 4123, connecting to db…), so it is
reachable via diagnostic and the public fields
rather than lost. Empty when the producing path captured nothing (a
streaming probe such as first_line, which never buffers).
The one-line Display message appends the last non-empty line of
diagnostic, capped at 200 bytes — just like
Exit — so a log line stays actionable without dumping
the captured streams.
Fields
OutputTooLarge
The captured output exceeded the
OverflowMode::Error fail-loud ceiling —
a line cap (max_lines), a byte
cap (max_bytes), or both. The
run itself may have succeeded; this error is raised by the consuming
path after the run completes.
The pipe is still fully drained (the child never blocks); output past the ceiling is counted (in the totals) but not retained.
Fields
NotReady
A readiness probe (RunningProcess::wait_for_line,
wait_for_port,
wait_for) did not pass within its
deadline — the line never appeared, the port never accepted, the check
never returned true, or the child exited before becoming ready.
Distinct from Timeout: a probe deadline is separate
from the run’s own Command::timeout, and a
failed probe does not kill the child — the caller decides what
happens next.
Fields
Parse
The process succeeded but its output could not be parsed into the
expected shape (e.g. malformed --json). Produced by the fallible-parse
helpers on CliClient.
message is caller-built and routinely embeds the unparsed output in
full, so — like the Exit streams — both Display and
Debug bound it to a 200-byte preview (B14); the complete text stays
reachable via the public field.
Fields
Unsupported
An operation is not supported by the active containment mechanism on this platform.
Raised by ProcessGroup::signal for any signal other than
Signal::Kill on Windows (Job Objects have no POSIX signals).
Cancelled
The run was cancelled via its CancellationToken
(Command::cancel_on) and its process
tree was killed.
Asymmetric with Timeout by design: a timeout is
captured (ProcessResult::timed_out) on the non-checking paths,
whereas a cancellation is always raised on every consuming path.
When a run both times out and is cancelled, cancellation wins (it is
checked first).
Unlike Timeout / Signalled,
this carries no captured streams (D12): cancellation is a deliberate
caller action that stops the run immediately. On the pre-spawn path (the
token was already cancelled) nothing was captured at all; on the consuming
verbs, any output captured before the kill is intentionally discarded —
the caller initiated the stop and knows why, so a partial diagnostic would
be noise. diagnostic returns None.
Signalled
The process was terminated by a signal (Unix) without producing an exit
code. signal carries the signal number when the platform reports one
(None on Windows or when the kernel does not expose it).
Distinct from Exit: a signal-terminated run has no exit
code to check — it is always a failure. Produced by
ensure_success and the
require_code path when the outcome is
Outcome::Signalled.
Carries whatever the run captured before the signal killed it (D12) — a
crashing tool’s partial stderr is often the diagnostic — reachable via
diagnostic and the public fields. The one-line
Display appends the bounded diagnostic tail, like Exit.
Fields
Stdin
The child ran but feeding its standard input failed for a reason other than the routine broken pipe.
Per [Decision 2], this is raised by the consuming paths only when the
run otherwise succeeded — a non-zero Exit, a
Signalled, or a Timeout is the
“realer” failure and wins (the stdin error is then dropped). A broken
pipe (EPIPE / ERROR_BROKEN_PIPE — the child closing stdin before
reading all of it) is routine and never surfaces. Diagnoses a
silently-truncated input the otherwise-successful child may have acted on.
[Decision 2]: the stdin source (Command::stdin)
is written on a background task; this carries that task’s failure.
The io-level classifiers (is_transient,
is_not_found,
is_permission_denied) deliberately return
false here: they classify spawn/launch conditions, and the run already
succeeded — a blanket retry would re-run a command that worked. Inspect
source directly if a stdin-specific retry is wanted.
Fields
Io(Error)
A low-level IO error from the crate’s own machinery — driving a child
(waiting for exit, issuing a kill), controlling a process group
(signalling, reaping, sampling stats), or reading/writing a cassette
file. It is not a spawn/launch condition (those are
Spawn / NotFound).
There is deliberately no blanket From<std::io::Error> (D13): the
crate never lets an arbitrary foreign io::Error fall into this variant
via ?. Every Io is constructed explicitly at a known site, so the
io-level classifiers (is_transient,
is_permission_denied) only ever see an
IO error the crate itself produced — never an unrelated one a caller’s
? happened to route through here.
Implementations§
Source§impl Error
impl Error
Sourcepub fn diagnostic(&self) -> Option<&str>
pub fn diagnostic(&self) -> Option<&str>
The best human-facing message for a failed run, trimmed of surrounding
whitespace: captured standard error if it carries text, otherwise the
captured standard output (where git puts CONFLICT … and git commit
puts nothing to commit). Covers the variants that capture streams — a
non-zero Exit, a Timeout (the partial
output of a hung-then-killed tool, D12), and a Signalled
crash. Returns None when there is no captured output to show — a silent
run (both streams blank) or a variant that carries none
(Spawn, Cancelled,
Parse, Io) — so a caller can fall back to
the Display message. For the raw, untrimmed stream
match on the variant’s fields directly.
Sourcepub fn is_not_found(&self) -> bool
pub fn is_not_found(&self) -> bool
Whether the program could not be located — it is not installed, not
on PATH, or the given path does not resolve to an executable. True for
NotFound and only that variant (D11): the launch
path funnels every program-not-found failure into NotFound, so this is
the one check a caller needs to surface a “command not installed?” hint.
false for every other variant — notably it does not fire for a
missing or invalid working directory (a Spawn carrying
NotFound/NotADirectory): a bad cwd
is not a missing program, so the hint would mislead. It is also false
for a program that is installed but can’t be executed directly (e.g. a
Windows .cmd/.bat that needs cmd.exe — surfaced as Spawn).
Sourcepub fn is_permission_denied(&self) -> bool
pub fn is_permission_denied(&self) -> bool
Whether this is a spawn/IO permission denial (EACCES/EPERM): the
binary isn’t executable, or the OS refused the launch. True for
Spawn / Io carrying
PermissionDenied; false
otherwise.
Sourcepub fn is_transient(&self) -> bool
pub fn is_transient(&self) -> bool
Whether this is a transient spawn/IO condition a bare retry can clear
— interrupted (EINTR), would-block (EAGAIN), a busy resource, a
text-file-busy executable mid-write (ETXTBSY), or a Windows sharing/lock
violation. Classifies the Spawn/Io IO
error only.
Scope: IO/spawn-level, never exit codes. Whether a tool’s non-zero
Exit is retryable is domain-specific (a git 128 is not
generically transient) — that stays the caller’s call. Timeout
is also excluded by design; compose it if wanted:
e.is_transient() || matches!(e, Error::Timeout { .. }).
Pairs with Command::retry:
cmd.retry(3, backoff, |e| e.is_transient()).
Trait Implementations§
Source§impl Debug for Error
Manual Debug (L22): bounds the Exit streams and redacts
the PATH value, so {e:?} / .unwrap() neither dumps a multi-MiB stream
nor logs an environment value. Every other variant mirrors what the derive
would print.
impl Debug for Error
Manual Debug (L22): bounds the Exit streams and redacts
the PATH value, so {e:?} / .unwrap() neither dumps a multi-MiB stream
nor logs an environment value. Every other variant mirrors what the derive
would print.
Source§impl Error for Error
impl Error for Error
Source§fn source(&self) -> Option<&(dyn Error + 'static)>
fn source(&self) -> Option<&(dyn Error + 'static)>
1.0.0 · Source§fn description(&self) -> &str
fn description(&self) -> &str
use the Display impl or to_string()