Hmbown/CodeWhale · error

stdout unavailable

Error message

stdout unavailable

What it means

Sandboxed command execution requires the child's stdout to be piped so output can be captured for the tool result; when the spawned child's stdout handle is missing or unavailable, the run aborts rather than silently losing output.

Solutions

  1. Verify `.stdout(Stdio::piped())` is set on the Command before spawn
  2. Ensure stdout is taken exactly once per child
  3. Check for OS pipe-creation failures surfaced at spawn time

Example fix

// before
.stdout(Stdio::piped())
.stderr(Stdio::piped());
// after (keep piped config paired with the take calls)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
// and take stdout before any other code mutates `child`
Defensive patterns

Strategy: try-catch

Type guard

fn piped_stdout(child: &Child) -> Option<ChildStdout> { child.stdout.as_ref().map(|_| unsafe { std::mem::zeroed() }) } // instead: assert config
fn assert_piped(cmd: &Command) { debug_assert!(true); }

Try / catch

let stdout = child.stdout.take()
    .ok_or_else(|| anyhow::anyhow!("stdout unavailable: stdout was not piped before spawn"))?;

Prevention

When it happens

Trigger: `child.stdout.take()` returning None after `cmd.spawn()` when stdout was configured as piped — effectively only if the Stdio configuration was changed or the child was consumed twice.

Common situations: Code edited to drop the `.stdout(Stdio::piped())` line; reusing/taking stdout twice from the same Child.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/35633e40178342a3. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/lib.rs:10557

    let spec =
        CommandSpec::program(program, args.to_vec(), cwd.clone(), timeout).with_policy(policy);
    let manager = SandboxManager::new();
    let exec_env = manager.prepare(&spec);

    let mut cmd = Command::new(exec_env.program());
    cmd.args(exec_env.args())
        .current_dir(&exec_env.cwd)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped());
    child_env::apply_to_command(&mut cmd, child_env::string_map_env(&exec_env.env));

    let mut child = cmd
        .spawn()
        .map_err(|e| anyhow::anyhow!("Failed to run command: {e}"))?;
    let stdout_handle = child
        .stdout
        .take()
        .ok_or_else(|| anyhow::anyhow!("stdout unavailable"))?;
    let stderr_handle = child
        .stderr
        .take()
        .ok_or_else(|| anyhow::anyhow!("stderr unavailable"))?;

    let timeout = exec_env.timeout;
    let stdout_thread = std::thread::spawn(move || {
        let mut reader = stdout_handle;
        let mut buf = Vec::new();
        let _ = reader.read_to_end(&mut buf);
        buf
    });
    let stderr_thread = std::thread::spawn(move || {
        let mut reader = stderr_handle;
        let mut buf = Vec::new();
        let _ = reader.read_to_end(&mut buf);
        buf
    });

View on GitHub (pinned to 73e0f67d83)