Hmbown/CodeWhale · error
shell command failed (status={}): {}
Error message
shell command failed (status={}): {} What it means
Thrown by ShellDispatcher when a dispatched shell command runs to completion but exits non-zero (crates/tui/src/shell_dispatcher.rs). The captured stderr, trimmed, is appended after the status code. Spawn failures are a different error ('failed to execute shell command'), so this one strictly means the process started, ran, and failed by its own exit code.
Source
Thrown at crates/tui/src/shell_dispatcher.rs:410
if self.restore {
let _ = crossterm::terminal::enable_raw_mode();
}
}
}
let _guard = FgRawModeGuard {
restore: raw_mode_was_enabled,
};
let mut cmd = self.build_command(shell_command);
cmd.current_dir(cwd);
let output = cmd
.output()
.with_context(|| format!("failed to execute shell command: {shell_command}"))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!(
"shell command failed (status={}): {}",
output.status,
stderr.trim()
);
}
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
Ok(stdout)
}
// -- Detection --------------------------------------------------------
fn detect_shell() -> ShellKind {
#[cfg(test)]
{
// Non-blocking on purpose. This runs inside the `LazyLock`
// initializer in `global_dispatcher()`, and a test that holds the
// env barrier can reach `global_dispatcher()` while another threadView on GitHub (pinned to 0c42157ee5)
Solutions
- Read the stderr in the message; it is the underlying tool's own output and names the root cause.
- Re-run the command directly in a terminal from the same cwd to see full, untrimmed output.
- If the non-zero exit is expected (grep no-match, diff), make the command exit 0 itself: append '|| true' or restructure.
- If the shell selection is wrong (command exists in another shell), fix shell detection; spawn failure is the sibling error to compare against.
Example fix
# before !grep -q TODO src/main.rs # exits 1 when no match -> error surfaced # after !grep -q TODO src/main.rs || true
Defensive patterns
Strategy: try-catch
Validate before calling
// Preflight: confirm the binary resolves in the dispatcher's shell and cwd
let check = Command::new("sh")
.arg("-c")
.arg(format!("command -v -- {binary:?} >/dev/null 2>&1"))
.current_dir(cwd)
.status()?;
if !check.success() {
eprintln!("{binary} not found on PATH for this shell/cwd");
} Try / catch
match dispatcher.run(&cmd, cwd).await {
Err(err) if err.to_string().starts_with("shell command failed (status=") => {
// the command ran and exited non-zero; stderr is embedded in the message
let root = err.to_string(); // surface it, do not retry blindly
}
Err(err) => { /* spawn/execution failure: check PATH and shell selection */ }
Ok(stdout) => { /* use stdout */ }
} Prevention
- Expect non-zero exits from signal-style commands (grep -q, diff --exit-code) and neutralize with '|| true' where the exit code is not an error.
- Run builds and tests from the same cwd the session uses so relative paths resolve identically.
- Read the embedded stderr before assuming the dispatcher itself misbehaved.
When it happens
Trigger: Running a bang command or shell tool whose process exits non-zero: failing builds ('!cargo build'), failing tests, grep with no matches (exit 1), 'git diff --exit-code' with changes. The dispatcher runs the command in the session cwd, so path-dependent commands that fail there land here.
Common situations: Build or test pipelines invoked from the TUI where the underlying tool already printed the real cause to stderr; commands that use exit codes as signals (grep -q, diff --exit-code); commands assuming a different working directory than the session cwd.
Related errors
- unsupported locale '{other}'
- invalid locale '{value}'
- unsupported theme '{other}'
- invalid theme '{value}'
- Invalid cost_currency '{other}': expected usd, cny, rmb, or
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/6c51c9d1e58b0fb2.
Report an issue: GitHub.