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 thread

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Read the stderr in the message; it is the underlying tool's own output and names the root cause.
  2. Re-run the command directly in a terminal from the same cwd to see full, untrimmed output.
  3. If the non-zero exit is expected (grep no-match, diff), make the command exit 0 itself: append '|| true' or restructure.
  4. 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

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


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/6c51c9d1e58b0fb2. Report an issue: GitHub.