Hmbown/CodeWhale · error · anyhow::Error

Failed to capture stdout

Error message

Failed to capture stdout

What it means

When a spawned child is handled with separated stdout/stderr reader threads (the non-combined, non-bounded path), child.stdout must be Some — i.e. the child was spawned with piped stdout. If stdout was not piped (take() yields None), the already-spawned child is terminated (terminate_unregistered_process, plus Windows job cleanup) and this error returns. It is an internal invariant: output capture was assumed but the spawn did not provide the pipe.

Source

Thrown at crates/tui/src/tools/shell.rs:2538

            {
                windows_job = attach_windows_job(&child, original_command);
            }

            let stdin_handle = child.stdin.take().map(StdinWriter::Pipe);

            let (stdout_thread, stderr_thread) =
                if let (Some(reader), Some(output)) = (combined_reader, bounded_output.as_ref()) {
                    (
                        Some(spawn_bounded_reader_thread(reader, Arc::clone(output))),
                        None,
                    )
                } else {
                    let stdout_handle = child.stdout.take().ok_or_else(|| {
                        #[cfg(windows)]
                        terminate_unregistered_process(&mut child, windows_job.as_ref());
                        #[cfg(not(windows))]
                        terminate_unregistered_process(&mut child);
                        anyhow!("Failed to capture stdout")
                    })?;
                    let stderr_handle = child.stderr.take().ok_or_else(|| {
                        #[cfg(windows)]
                        terminate_unregistered_process(&mut child, windows_job.as_ref());
                        #[cfg(not(windows))]
                        terminate_unregistered_process(&mut child);
                        anyhow!("Failed to capture stderr")
                    })?;
                    (
                        Some(spawn_reader_thread(
                            stdout_handle,
                            Arc::clone(&stdout_buffer),
                        )),
                        stderr_buffer
                            .as_ref()
                            .map(|buffer| spawn_reader_thread(stderr_handle, Arc::clone(buffer))),
                    )
                };

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Retry the command once — transient spawn misconfiguration is unlikely but cheap to rule out.
  2. If reproducible, report upstream with the exact command, background/tty flags, and platform.
  3. As a workaround, run the command in background mode with bounded output, which uses the combined reader path.
Defensive patterns

Strategy: retry

Try / catch

let out = match exec_shell(cmd, dir, opts).await {
    Ok(o) => o,
    Err(e) if e.to_string().contains("Failed to capture stdout") => {
        // reader-thread invariant broke; retry once, then fall back to bounded/background mode
        exec_shell(cmd, dir, opts).await
            .or_else(|_| exec_shell_background_bounded(cmd, dir))?
    }
    Err(e) => return Err(e),
};

Prevention

When it happens

Trigger: A spawn path or platform behavior where the child is created without Stdio::piped() stdout but the separated-reader branch is taken; e.g. an internal regression or a mode mismatch between requested capture and spawn configuration.

Common situations: Rare; users normally cannot trigger it via the tool API — output capture is requested implicitly. Would surface after code changes to spawn options.

Related errors


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