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

  1. Check system file descriptor limits (ulimit -n) and raise if exhausted
  2. Reduce the number of concurrently spawned sessions to avoid pipe resource exhaustion
  3. Verify the platform supports piped stdio for child processes
  4. 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

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


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/0fde28e0c35f8d4b. Report an issue: GitHub.