affaan-m/ECC · error · anyhow::Error
Child stdout was not piped
Error message
Child stdout was not piped
What it means
When spawning a child process for a session with Stdio::piped() configured for stdout, the code expects child.stdout to be Some(...). If the platform does not provide a piped handle despite the Stdio::piped() configuration, child.stdout is None and the function bails after killing the child to avoid a zombie process.
Source
Thrown at ecc2/src/session/runtime.rs:158
mut command: Command,
output_store: SessionOutputStore,
heartbeat_interval: std::time::Duration,
) -> Result<ExitStatus> {
let db_writer = DbWriter::start(db_path, session_id.clone());
let result = async {
let mut child = command
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.with_context(|| format!("Failed to start process for session {}", session_id))?;
let stdout = match child.stdout.take() {
Some(stdout) => stdout,
None => {
let _ = child.kill().await;
let _ = child.wait().await;
anyhow::bail!("Child stdout was not piped");
}
};
let stderr = match child.stderr.take() {
Some(stderr) => stderr,
None => {
let _ = child.kill().await;
let _ = child.wait().await;
anyhow::bail!("Child stderr was not piped");
}
};
let pid = child
.id()
.ok_or_else(|| anyhow::anyhow!("Spawned process did not expose a process id"))?;
db_writer.update_pid(Some(pid)).await?;
db_writer.update_state(SessionState::Running).await?;
db_writer.touch_heartbeat().await?;
View on GitHub (pinned to 01e15490f0)
Solutions
- Check system file descriptor limits (ulimit -n) and raise if exhausted
- Reduce the number of concurrently spawned sessions to avoid pipe resource exhaustion
- Verify the platform supports piped stdio for child processes
- If this is a transient resource issue, retry the spawn after reducing concurrent sessions
Defensive patterns
Strategy: try-catch
Try / catch
match spawn_session_process(command, session_id).await {
Ok((stdout, stderr, pid)) => Ok((stdout, stderr, pid)),
Err(e) if e.to_string().contains("not piped") => {
tracing::error!("stdio pipe allocation failed for session {session_id}; check fd limits");
Err(e)
}
Err(e) => Err(e),
} Prevention
- Monitor system file descriptor limits (ulimit -n) and raise them if sessions fail to spawn
- Reduce concurrent session count if pipe resources are exhausted
- Implement retry logic for spawn failures that may be transient resource issues
When it happens
Trigger: Calling the session spawn path where command.stdout(Stdio::piped()).spawn() succeeds but the returned child.stdout is None.
Common situations: Platform-specific failure in pipe creation that does not surface as a spawn error. Resource exhaustion (too many open file descriptors preventing pipe allocation). Bug or edge case in the Rust standard library's process spawning on the target platform.
Related errors
- Child stderr was not piped
- Command "${commandName}" terminated by signal ${result.signa
- ${command} ${args.join(' ')} failed: ${result.error.message}
- ${command} ${args.join(' ')} failed: ${(result.stderr || res
- ECC runner did not expose a process id
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/0fde28e0c35f8d4b.
Report an issue: GitHub.