affaan-m/ECC · error

ECC runner did not expose a process id

Error message

ECC runner did not expose a process id

What it means

After spawning the ECC runner child process, the code reads child.id(); on some platforms/spawn failure modes std returns None even though spawn() reported success. The manager treats an unknown pid as fatal because session stop/monitoring depends on it.

Solutions

  1. Retry the spawn once; transient races usually resolve
  2. Check that the runner executable at current_exe-relative path exists and is not blocked by AV/EDR
  3. Log the OS-level spawn error and fall back to running the runner synchronously if pid tracking is not required

Example fix

// before
child.id().ok_or_else(|| anyhow::anyhow!("ECC runner did not expose a process id"))?;
// after
let pid = child.id().with_context(|| format!("ECC runner exited before pid could be read (status: {:?})", child.try_wait()))?;
Defensive patterns

Strategy: try-catch

Validate before calling

// no pre-call check possible for child.id(); verify executable first
assert!(current_exe.join("runner").exists(), "runner binary missing");

Try / catch

match spawn_runner().await {
    Ok(pid) => track(pid),
    Err(e) if e.to_string().contains("did not expose a process id") => {
        eprintln!("runner exited before pid capture; check runner logs");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: command.spawn() succeeds but the Child handle reports no pid — typically on Windows where pid exposure can fail, or when the process exits/reaps extremely quickly between spawn and id().

Common situations: Windows targets with restricted process handles; antivirus/EDR intercepting the spawned binary; spawning a binary path that dies instantly.

Related errors


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

Appendix: source

Thrown at ecc2/src/session/manager.rs:3027

        .arg(session_id)
        .arg("--task")
        .arg(task)
        .arg("--agent")
        .arg(agent_type)
        .arg("--cwd")
        .arg(working_dir)
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::from(stderr_log));
    configure_background_runner_command(&mut command);

    let child = command
        .spawn()
        .with_context(|| format!("Failed to spawn ECC runner from {}", current_exe.display()))?;

    child
        .id()
        .ok_or_else(|| anyhow::anyhow!("ECC runner did not expose a process id"))?;
    Ok(())
}

fn background_runner_stderr_log_path(working_dir: &Path, session_id: &str) -> PathBuf {
    working_dir
        .join(".claude")
        .join("ecc2")
        .join("logs")
        .join(format!("{session_id}.runner-stderr.log"))
}

#[cfg(windows)]
fn detached_creation_flags() -> u32 {
    const DETACHED_PROCESS: u32 = 0x0000_0008;
    const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
    DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP
}

View on GitHub (pinned to 8321021c54)