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
- Confirm .stdout(Stdio::piped()) is set on the same Command builder right before spawn
- Ensure the stdout handle is taken exactly once and not earlier in the flow
- 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
- Keep Stdio::piped() for both streams directly on the Command builder
- Take each pipe handle exactly once, immediately after spawn
- Add a unit test asserting take() returns Some after a piped spawn
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
- stderr unavailable
- MCP connection disappeared for {server_name}
- Failed to store MCP connection for {server_name}
- failed to parse git log output
- failed to parse serialized config for comment merge; file co
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/35633e40178342a3.
Report an issue: GitHub.