{"record":{"id":"61c60333f9cbf4fc","repo":"astrid-runtime/astrid","slug":"shutdown-stage-gateway-process-reap-pid-is-sti","errorCode":null,"errorMessage":"shutdown stage gateway.process_reap: PID {} is still alive","messagePattern":"shutdown stage gateway\\.process_reap: PID (.+?) is still alive","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/mcp/lifecycle.rs","lineNumber":710,"sourceCode":"    R: tokio::io::AsyncRead + Unpin,\n{\n    let mut line = Vec::new();\n    for _ in 0..=MAX_CONTROL_BYTES {\n        let byte = reader\n            .read_u8()\n            .await\n            .context(\"failed to read control frame\")?;\n        if byte == b'\\n' {\n            return Ok(line);\n        }\n        line.push(byte);\n    }\n    anyhow::bail!(\"MCP gateway control frame is missing or too large\")\n}\n\nasync fn remove_dead_gateway_markers(record: &GatewayReady) -> Result<()> {\n    if crate::commands::daemon_control::is_process_alive(record.pid) {\n        anyhow::bail!(\n            \"shutdown stage gateway.process_reap: PID {} is still alive\",\n            record.pid\n        );\n    }\n    let lifecycle = try_acquire_gateway_lifecycle()?;\n    let Some(lifecycle) = lifecycle else {\n        anyhow::bail!(\n            \"shutdown stage gateway.lifecycle_fence: a successor gateway lifecycle remains active\"\n        );\n    };\n    let socket = gateway_socket_path()?;\n    match astrid_core::local_transport::connect_outcome(&socket)\n        .await\n        .context(\"shutdown stage gateway.listener_probe\")?\n    {\n        astrid_core::local_transport::ConnectOutcome::Connected(_) => {\n            anyhow::bail!(\n                \"shutdown stage gateway.listener_absence: a gateway is still accepting connections\"","sourceCodeStart":692,"sourceCodeEnd":728,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/mcp/lifecycle.rs#L692-L728","documentation":"remove_dead_gateway_markers is meant to clean up markers for a gateway believed dead, but the PID from the ready record is still alive, so cleanup is refused to avoid clobbering a functioning gateway's state.","triggerScenarios":"wait_for_gateway or stop_ready_gateway determines the ready record is stale and calls remove_dead_gateway_markers, yet daemon_control::is_process_alive(record.pid) returns true — the process outlived its recorded state.","commonSituations":"PID reuse: an unrelated process now owns the recorded PID; the gateway is actually fine but its ready file was corrupted/replaced; shutdown raced process exit between liveness checks.","solutions":["Check what the PID actually is (ps -p <pid>) — if it's the real gateway, use the normal stop path instead of marker cleanup","If it's an unrelated process (PID reuse), remove the stale ready/marker files manually once verified","Re-run the operation; transient races between liveness checks often resolve on retry","Ensure only one client manages the gateway to avoid racing shutdowns"],"exampleFix":"// before\nremove_dead_gateway_markers(&record).await?;\n// after: verify the PID isn't the live gateway\nif is_process_alive(record.pid) && process_is_gateway(record.pid) {\n    stop_ready_gateway(&record).await?; // proper shutdown path\n} else {\n    remove_dead_gateway_markers(&record).await?;\n}","handlingStrategy":"validation","validationCode":"if daemon_control::is_process_alive(record.pid) {\n    // verify it really is the gateway before any cleanup\n    anyhow::ensure!(process_exe(record.pid)? == record.exe, \"PID alive but not the gateway\");\n}","typeGuard":"fn marker_removal_is_safe(record: &GatewayReady) -> bool {\n    !crate::commands::daemon_control::is_process_alive(record.pid)\n}","tryCatchPattern":"match remove_dead_gateway_markers(&record).await {\n    Err(e) if e.to_string().contains(\"still alive\") => {\n        // process alive: take the owned stop path instead of cleanup\n        stop_ready_gateway(&record).await?;\n    },\n    other => other?,\n}","preventionTips":["Don't forcibly kill gateway processes outside the library","Re-check PID identity after any suspected PID reuse","Run a single supervisor for gateway lifecycle"],"tags":["process-management","pid-reuse","cleanup"],"backgroundTag":"invalid-state-transition","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}