Hmbown/CodeWhale · warning

stdout unavailable

Error message

stdout unavailable

What it means

Defensive invariant on the stdout pipe: the Command is configured with .stdout(Stdio::piped()) before spawn, so child.stdout.take() immediately after must yield Some. It cannot fire through normal usage of the shipped code; it only appears if a fork or refactor removes the piped stdout or takes the handle twice.

Source

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

    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 8880682c63)

Solutions

  1. Confirm .stdout(Stdio::piped()) is set on the same Command builder right before spawn
  2. Ensure the stdout handle is taken exactly once and not earlier in the flow
  3. If the file was patched, restore the original spawn block from upstream

Example fix

// before (broken fork): stdout not piped
let mut cmd = Command::new(exec_env.program());
cmd.args(exec_env.args());
// after
let mut cmd = Command::new(exec_env.program());
cmd.args(exec_env.args()).stdout(Stdio::piped()).stderr(Stdio::piped());
Defensive patterns

Strategy: try-catch

Try / catch

// Treat as an internal invariant breach: report and abort, do not retry
match child.stdout.take() {
    Some(handle) => handle,
    None => return Err(anyhow::anyhow!(
        "internal error: stdout pipe missing after piped spawn (report upstream)"
    )),
}

Prevention

When it happens

Trigger: Not reachable via public inputs in the current source. Appears in modified code that drops .stdout(Stdio::piped()) from the builder, conditionally sets it, or calls take() on stdout more than once before this point.

Common situations: Local patches that inherit stdout for debugging, refactors moving pipe setup behind a flag, or duplicated take() calls after restructuring spawn handling.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/35633e40178342a3. Report an issue: GitHub.