#[non_exhaustive]pub enum Error {
Show 13 variants
#[non_exhaustive] Spawn {
program: String,
source: Error,
},
#[non_exhaustive] NotFound {
program: String,
searched: Option<String>,
},
CassetteMiss {
program: String,
},
#[non_exhaustive] Exit {
program: String,
code: i32,
stdout: String,
stderr: String,
stdout_bytes: Option<Vec<u8>>,
},
#[non_exhaustive] Timeout {
program: String,
timeout: Duration,
stdout: String,
stderr: String,
stdout_bytes: Option<Vec<u8>>,
},
#[non_exhaustive] OutputTooLarge {
program: String,
max_lines: Option<usize>,
max_bytes: Option<usize>,
total_lines: usize,
total_bytes: usize,
},
NotReady {
program: String,
timeout: Duration,
},
#[non_exhaustive] Parse {
program: String,
message: String,
},
Unsupported {
operation: String,
},
Cancelled {
program: String,
},
#[non_exhaustive] Signalled {
program: String,
signal: Option<i32>,
stdout: String,
stderr: String,
stdout_bytes: Option<Vec<u8>>,
},
#[non_exhaustive] 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: 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
#[non_exhaustive]Spawn
The child process could not be started (binary not found, permission denied, …).
Fields
This variant is marked as non-exhaustive
#[non_exhaustive]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, 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
This variant is marked as non-exhaustive
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. 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.
#[non_exhaustive]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
This variant is marked as non-exhaustive
stdout: StringCaptured standard output, in full. Not shown in the Display
message; kept for callers that need a stdout-borne failure message.
For the raw-bytes helper (output_bytes) this is a lossy UTF-8
decode of stdout — see this variant’s stdout_bytes field for
the exact bytes on that path.
stderr: StringCaptured standard error, in full. Only its last non-empty
line (bounded) appears in the Display message — the complete
captured text lives here, never poisoning a log line.
stdout_bytes: Option<Vec<u8>>The exact captured stdout bytes, when the producing path captured
raw bytes rather than already-decoded text — Some for a checking
verb built over output_bytes
(e.g. output_bytes().await?.ensure_success()?), None for the
text path (output_string/run/checked/…), where stdout above
is already the complete, non-lossy text and there is no separate
“raw” form to recover. When Some, these bytes are the exact
pre-decode stdout — stdout is a lossy UTF-8 decode of this same
data, so the two may differ when the stream was not valid UTF-8.
Read via Error::stdout_bytes.
#[non_exhaustive]Timeout
The process exceeded its configured timeout and was killed.
Carries whatever the run captured before the deadline killed it:
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
This variant is marked as non-exhaustive
stdout: StringStandard output captured before the kill, in full. Not shown in the
Display message (only its bounded diagnostic tail is). Empty when
the path captured nothing. For the raw-bytes helper this is a lossy
UTF-8 decode — see this variant’s stdout_bytes field for the
exact bytes on that path.
#[non_exhaustive]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
This variant is marked as non-exhaustive
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
#[non_exhaustive]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 try_parse on Command,
ProcessRunnerExt,
CliClient, and Pipeline (or any
parser the caller maps into this variant).
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; the complete text stays
reachable via the public field.
Fields
This variant is marked as non-exhaustive
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: 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.
#[non_exhaustive]Signalled
The process was terminated by a signal (Unix only) without producing an
exit code. signal carries the signal number when the kernel reports one,
else None. On Windows a killed process reports Exit
with a platform code, never this — a live Signalled cannot occur there; it
arises only from a ScriptedRunner or a
record-feature cassette replay, which report Signalled(None) to mirror
Unix.
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 — 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
This variant is marked as non-exhaustive
stdout: StringStandard output captured before the kill, in full. Not shown in the
Display message (only its bounded diagnostic tail is). For the
raw-bytes helper this is a lossy UTF-8 decode of stdout — see this
variant’s stdout_bytes field for the exact bytes on that path.
#[non_exhaustive]Stdin
The child ran but feeding its standard input failed for a reason other than the routine broken pipe.
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.
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
This variant is marked as non-exhaustive
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>: 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), 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 stdout(&self) -> Option<&str>
pub fn stdout(&self) -> Option<&str>
The captured standard output, for the variants that carry a stream — a
non-zero Exit, a Timeout (partial
output before the kill), or a Signalled crash —
None for every other variant. The raw stream in full (untrimmed); for
the best one-line message use diagnostic, for both
streams joined combined. Reads the stream off the error
without destructuring a #[non_exhaustive] variant.
Sourcepub fn stderr(&self) -> Option<&str>
pub fn stderr(&self) -> Option<&str>
The captured standard error, for the stream-bearing variants (see
stdout); None otherwise. The raw stream in full.
Sourcepub fn stdout_bytes(&self) -> Option<&[u8]>
pub fn stdout_bytes(&self) -> Option<&[u8]>
The exact captured stdout bytes, when available — Some only for a
stream-bearing variant (Exit / Timeout
/ Signalled) produced by a checking verb built over
output_bytes (e.g.
output_bytes().await?.ensure_success()?); None for every other
variant, and for a stream-bearing variant produced on the text path
(output_string/run/checked/…), where stdout
above is already the complete, non-lossy text. When Some, these bytes
are the exact pre-decode stdout that stdout is a lossy
UTF-8 decode of — the two differ when the stream was not valid UTF-8.
Sourcepub fn combined(&self) -> Option<String>
pub fn combined(&self) -> Option<String>
Standard output followed by standard error, joined — the Error twin of
ProcessResult::combined. Some for the
stream-bearing variants, None otherwise. A \n is inserted between the
streams only when both are non-empty and stdout doesn’t already end in one.
Use when a tool interleaves diagnostics across both streams, so a single
diagnostic stream (stderr else stdout) would miss a
marker on the other.
Sourcepub fn program(&self) -> Option<&str>
pub fn program(&self) -> Option<&str>
The program (the CLI tool) this error is attributed to — Some for every
variant that names one (all except Unsupported,
Io, and the limits-only ResourceLimit), None otherwise.
The Error twin of
ProcessResult::program: the one
cross-cutting datum a wrapper routes or logs on, read without destructuring
a #[non_exhaustive] variant.
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: 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() || e.is_timeout().
Pairs with Command::retry:
cmd.retry(3, backoff, |e| e.is_transient()).
Sourcepub fn code(&self) -> Option<i32>
pub fn code(&self) -> Option<i32>
The process exit code for a non-zero Exit; None for
every other variant (a timeout or a signal kill carries no exit code).
The same code() the crate’s other disposition types expose
(ProcessResult::code /
Outcome::code, and RunProfile::code under the
stats feature), so a code is one name everywhere. Reads the code off the
error without destructuring the variant.
Sourcepub fn signal(&self) -> Option<i32>
pub fn signal(&self) -> Option<i32>
The signal number for a Signalled run terminated
with a known signal (Unix only); None for every other variant and
for a signal the kernel didn’t expose — the Error twin of
ProcessResult::signal /
Outcome::signal. Reads the signal off the error
without destructuring the variant.
Sourcepub fn is_timeout(&self) -> bool
pub fn is_timeout(&self) -> bool
Whether the run was killed because it exceeded its
Command::timeout — i.e. this is a
Timeout. First-class here so the
is_transient retry-composition example can read
e.is_transient() || e.is_timeout() rather than matching the variant by hand.
The Error twin of the crate-wide deadline predicate
ProcessResult::timed_out (named
is_timeout here to sit alongside the error’s is_* predicate family).
Sourcepub fn is_cancelled(&self) -> bool
pub fn is_cancelled(&self) -> bool
Whether the run was deliberately cancelled via its
Command::cancel_on token — i.e. this is a
Cancelled. A caller that initiated the stop can
swallow it rather than log or retry it as a real failure (the same
disposition Supervisor treats as terminal), without
destructuring the variant.
Sourcepub fn is_signalled(&self) -> bool
pub fn is_signalled(&self) -> bool
Whether the run was killed by a signal — i.e. this is a
Signalled. Distinct from signal:
this is true even when the kernel didn’t expose a number (a Unix kill
where signal() is None, or any platform’s signal disposition), so it is
the reliable “died by a signal?” check — the predicate twin of
is_timeout / is_cancelled,
without destructuring the variant.
Sourcepub fn parse(program: impl Into<String>, message: impl Into<String>) -> Error
pub fn parse(program: impl Into<String>, message: impl Into<String>) -> Error
Construct a Parse from a caller-supplied parser’s own
failure message. Unlike exit/timeout/signalled/spawn/not_found/
stdin above (#[doc(hidden)] insulated constructors meant for test
doubles), this one is left on the documented public surface: an
external parser that inspects a tool’s output outside this crate’s own
try_parse helpers has no other way to report a parse failure as an
Error::Parse once the variant is #[non_exhaustive], and that path is
a normal production use, not just a test-doubling convenience.
Trait Implementations§
Source§impl Debug for Error
Manual Debug: 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: 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()