Hmbown/CodeWhale · warning
stderr unavailable
Error message
stderr unavailable
What it means
Defensive invariant on the stderr pipe, symmetric to the stdout one: .stderr(Stdio::piped()) is set before spawn, so child.stderr.take() must return Some. It cannot fire through normal usage; it indicates a regression where the stderr pipe was not requested or the handle was already consumed.
Source
Thrown at crates/tui/src/lib.rs:8939
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
});
if let Some(status) = child.wait_timeout(timeout)? {
let stdout = stdout_thread.join().unwrap_or_default();
let stderr = stderr_thread.join().unwrap_or_default();View on GitHub (pinned to 8880682c63)
Solutions
- Confirm .stderr(Stdio::piped()) is set on the Command before spawn
- Ensure the stderr handle is taken exactly once, in the same place as the stdout take
- Restore the unmodified spawn block if the file was patched for debugging
Example fix
// before (broken fork): stderr inherited let mut cmd = Command::new(exec_env.program()); // after let mut cmd = Command::new(exec_env.program()); cmd.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.stderr.take() {
Some(handle) => handle,
None => return Err(anyhow::anyhow!(
"internal error: stderr pipe missing after piped spawn (report upstream)"
)),
} Prevention
- Keep Stdio::piped() for both streams directly on the Command builder
- Take stdout and stderr handles in one place, exactly once each
- Cover the take-both-handles path with a spawn integration test
When it happens
Trigger: Not reachable via public inputs in the current source. Appears when a fork removes .stderr(Stdio::piped()), makes it conditional, or takes the stderr handle earlier (for example, to route stderr through a different reader).
Common situations: Debug patches that let stderr leak to the terminal, refactors that move pipe handling into a helper, double take() after restructuring.
Related errors
- stdout 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/7edef7522177c75c.
Report an issue: GitHub.