astrid-runtime/astrid · error

ps failed while inspecting MCP processes

Error message

ps failed while inspecting MCP processes

What it means

gc() shells out to `ps -axo pid=,ppid=,command=` to enumerate processes for reaping orphaned MCP servers. If ps runs but exits non-zero, gc cannot enumerate processes and bails with this error. (Launch failure of ps itself produces the context-wrapped 'failed to inspect MCP processes with ps' error instead.)

Source

Thrown at crates/astrid-cli/src/commands/mcp/lifecycle.rs:905

        .get(index)
        .is_some_and(|token| matches!(command_file_name(token), "astrid" | "aos"))
}

fn is_reapable_mcp(command: &str) -> bool {
    is_long_mcp_serve(command) || is_mcp_attach(command)
}

/// Remove orphaned long-timeout `mcp serve` and `mcp attach` processes.
///
/// Never signals Python `aos-mcp-frame` processes. Those abort on 3.14 if a
/// SIGKILL races `Buffered_close`; attach children are the reap target.
pub(crate) fn gc() -> Result<ExitCode> {
    let output = Command::new("ps")
        .args(["-axo", "pid=,ppid=,command="])
        .output()
        .context("failed to inspect MCP processes with ps")?;
    if !output.status.success() {
        anyhow::bail!("ps failed while inspecting MCP processes");
    }
    let listing = String::from_utf8_lossy(&output.stdout);
    let mut reaped = 0_u32;
    for row in listing.lines().filter_map(parse_process_row) {
        if row.pid == std::process::id() || !is_reapable_mcp(&row.command) {
            continue;
        }
        let parent_dead =
            row.ppid == 1 || !crate::commands::daemon_control::is_process_alive(row.ppid);
        if !parent_dead {
            continue;
        }
        // Re-read the command immediately before signalling to avoid killing a
        // recycled PID that no longer belongs to a reapable MCP shim.
        if !process_command(row.pid).is_some_and(|command| is_reapable_mcp(&command)) {
            continue;
        }
        #[cfg(unix)]

View on GitHub (pinned to affd8760f4)

Solutions

  1. Verify `ps -axo pid=,ppid=,command=` runs successfully in your shell; fix or replace the failing ps.
  2. Install a full procps ps in the container image (e.g. `apt-get install procps` / Alpine `apk add procps`).
  3. Run astrid outside the sandbox/policy that blocks ps.
  4. Report the issue: gc could fall back to /proc scanning on Linux.

Example fix

// before (Alpine container)
astrid mcp gc   # -> ps failed while inspecting MCP processes
// after
apk add procps && astrid mcp gc
Defensive patterns

Strategy: fallback

Validate before calling

let check = std::process::Command::new("ps")
    .args(["-axo", "pid=,ppid=,command="])
    .output()?;
if !check.status.success() {
    eprintln!("ps incompatible; install procps before running astrid mcp gc");
}

Try / catch

if let Err(e) = astrid::mcp::gc() {
    if e.to_string().contains("ps failed") {
        eprintln!("install procps (apt-get install procps / apk add procps) or run on a host with full ps");
    }
}

Prevention

When it happens

Trigger: Running `astrid mcp gc` on a system where the `ps` binary exists but exits with failure — e.g. a restricted/incompatible ps (busybox variants lacking -axo), a broken /proc, or ps blocked by security policy.

Common situations: Minimal containers with busybox ps that rejects `-axo pid=,ppid=,command=`; hardened environments blocking ps via seccomp/AppArmor; corrupted PATH picking a shim ps that fails.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/960a25ba9eb2af82. Report an issue: GitHub.