affaan-m/ECC · error

Spawned process did not expose a process id

Error message

Spawned process did not expose a process id

What it means

capture_command_output spawns the session command and immediately needs its pid to record via db_writer.update_pid. child.id() returning None (process already reaped or platform pid exposure failure) aborts capture with this error, since heartbeat/stop tracking is pid-based.

Solutions

  1. Verify the command runs standalone before scheduling it in a session
  2. Capture and report child.try_wait() status alongside the missing pid to reveal the instant exit
  3. Retry the spawn; if pid tracking is optional, degrade gracefully instead of failing the whole capture

Example fix

// before
let pid = child.id().ok_or_else(|| anyhow::anyhow!("Spawned process did not expose a process id"))?;
// after
let pid = child.id().with_context(|| format!("spawned process exited immediately: {:?}", child.try_wait()))?;
Defensive patterns

Strategy: try-catch

Validate before calling

// dry-run the command first
let ok = Command::new("sh").arg("-c").arg("command -v <cmd>").status().map(|s| s.success()).unwrap_or(false);
assert!(ok, "command not runnable");

Try / catch

match capture_command_output(...).await {
    Err(e) if e.to_string().contains("did not expose a process id") => {
        eprintln!("process exited before pid capture; inspect command args and output");
    }
    Err(e) => return Err(e),
    Ok(v) => v,
}

Prevention

When it happens

Trigger: The spawned command exits instantly (bad args, missing interpreter) so the Child has no id; or a platform-level spawn quirk (Windows) prevents pid exposure right after a successful spawn().

Common situations: Command string referencing a nonexistent binary wrapped by a shell that exits immediately; stdin/stderr piping misconfiguration causing early exit; race with very short-lived processes.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/1fbd997496e2fbff. Report an issue: GitHub.

Appendix: source

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

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

        let stdout_task = tokio::spawn(capture_stream(
            session_id.clone(),

View on GitHub (pinned to 8321021c54)