affaan-m/ECC · error · anyhow::Error

Child stderr was not piped

Error message

Child stderr was not piped

What it means

Same invariant as stdout: capture_command_output requires piped stderr, and child.stderr.take() returned None. The child is killed and waited before the error is raised, indicating the Command was built without Stdio::piped for stderr.

Solutions

  1. Spawn the child process with stderr set to piped before reading it.
  2. Ensure no earlier code consumed or detached the stderr handle.
Defensive patterns

Strategy: type-guard

When it happens

Trigger: Thrown at ecc2/src/session/runtime.rs:166 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of affaan-m/ECC@06c5e118c4 (2026-08-18). Data as JSON: /api/errors/eacf1222c27f8a18. Report an issue: GitHub.

Appendix: source

Thrown at ecc2/src/session/runtime.rs:166

            .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?;

        let heartbeat_writer = db_writer.clone();
        let heartbeat_task = tokio::spawn(async move {
            let mut ticker = time::interval(heartbeat_interval);
            ticker.set_missed_tick_behavior(MissedTickBehavior::Delay);
            loop {
                ticker.tick().await;
                if heartbeat_writer.touch_heartbeat().await.is_err() {
                    break;

View on GitHub (pinned to 06c5e118c4)