Hmbown/CodeWhale · error

Failed to capture stderr

Error message

Failed to capture stderr

What it means

Same failure class as the stdout case but for the child's stderr: after spawning a non-TTY child, `child.stderr.take()` returned None, so output capture is impossible. The tool kills the unregistered child immediately and errors out rather than running a process whose diagnostics would be silently lost.

Solutions

  1. Raise the file-descriptor limit (ulimit -n) and retry the command
  2. Re-run — transient pipe-creation failures may not recur
  3. Check that the sandbox/job-object configuration preserves stdio for children
  4. Investigate descriptor leaks in long-running sessions if it reproduces

Example fix

// caller-side mitigation: ensure fd headroom before spawning
// before
shell.run("cargo test", { "background": false })
// after
// raise fd limit (ulimit -n 4096) or free leaked descriptors, then retry the same call
Defensive patterns

Strategy: retry

Validate before calling

// ensure fd headroom before spawning (stderr pipe needs an fd too)
// ulimit -n should comfortably exceed current usage + per-child pipe fds

Try / catch

// treat it as transient spawn failure
match result {
    Err(e) if e.to_string().contains("Failed to capture stderr") => retry_with_backoff(cmd, 2),
    other => other,
}

Prevention

When it happens

Trigger: Spawning a synchronous non-TTY command where the OS failed to create the stderr pipe (fd exhaustion, resource limits, platform spawn anomaly), making `Child::stderr` None.

Common situations: File-descriptor exhaustion on busy systems; containers or sandboxes restricting pipe allocation; concurrent processes leaking descriptors.

Related errors


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

Appendix: source

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

                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))),
                    )
                };

            (
                ShellChild::Process(child),
                stdin_handle,
                stdout_thread,
                stderr_thread,
            )

View on GitHub (pinned to 73e0f67d83)