bensadeh/tailspin · error · anyhow::Error
Could not capture stdout of spawned process
Error message
Could not capture stdout of spawned process
What it means
This error is thrown when the spawned child process has no piped stdout to capture. The library sets stdout(Stdio::piped()) before spawning, so take_stdout() returning None indicates the pipe could not be established or was already consumed — without it the CommandReader cannot stream output.
Solutions
- Verify .stdout(Stdio::piped()) is called on the Command before spawning
- Ensure take_stdout() is called exactly once and before waiting on the child
- Check no wrapper/indirection redirects the child's stdout away from the pipe
- If the error persists, log the spawn configuration and upgrade/patch the shared-child usage
Example fix
// before
let mut sh = Command::new(shell); // no stdout configuration
// after
sh.arg("-c").arg(trap_command).stdout(Stdio::piped()); Defensive patterns
Strategy: validation
Validate before calling
// Before constructing the reader, confirm the command will have a piped stdout:
let mut sh = Command::new("sh");
sh.arg("-c").arg(cmd).stdout(Stdio::piped());
assert!(sh.get_stdout().is_some() || std::env::var("CI").is_ok(),
"stdout must be piped before spawn"); Try / catch
let reader = CommandReader::new(cmd).map_err(|e| {
if e.to_string().contains("Could not capture stdout") {
eprintln!("stdout pipe missing; check Stdio config");
}
e
})?; Prevention
- Always call .stdout(Stdio::piped()) on the child Command before spawn
- Call take_stdout() exactly once, immediately after spawn
- Do not redirect the child's stdout to a file or inherit it in wrapper code
- Review diffs around spawn configuration when this error appears after a refactor
When it happens
Trigger: Calling CommandReader::new (via spawn_command) when SharedChild::spawn succeeded but child.take_stdout() returns None. Practically this happens only if the stdout pipe was not set to piped before spawn or was taken elsewhere.
Common situations: Code changes that remove or reorder the .stdout(Stdio::piped()) call; spawning a command whose stdout is redirected to a file or /dev/null by an outer wrapper; duplicated take_stdout() calls in modified code.
Related errors
AI-assisted analysis of bensadeh/tailspin@8ecaa9a8a1 (2026-09-13).
Data as JSON: /api/errors/b2e3b22b2a682807.
Report an issue: GitHub.
Appendix: source
Thrown at src/io/reader/command.rs:59
}
}
#[cfg(not(windows))]
fn spawn_command(command: String) -> Result<CommandReader> {
use crate::io::reader::line_batcher::BUF_READER_CAPACITY;
use anyhow::Context;
use std::process::{Command, Stdio};
let trap_command = format!("trap '' INT; {command}");
let mut sh = Command::new("sh");
sh.arg("-c").arg(trap_command).stdout(Stdio::piped());
let child = SharedChild::spawn(&mut sh).context("Could not spawn process")?;
let stdout = child
.take_stdout()
.ok_or_else(|| anyhow!("Could not capture stdout of spawned process"))?;
let reader = BufReader::with_capacity(BUF_READER_CAPACITY, stdout);
Ok(CommandReader {
reader,
child: Arc::new(child),
initial_read_complete_sent: false,
})
}
#[cfg(windows)]
fn spawn_command(_command: String) -> Result<CommandReader> {
Err(anyhow!("The --exec flag is not supported on Windows"))
}
impl Drop for CommandReader {
fn drop(&mut self) {
let _ = self.child.kill();View on GitHub (pinned to 8ecaa9a8a1)