{"record":{"id":"376a1f36b311de50","repo":"astrid-runtime/astrid","slug":"shutdown-stage-gateway-startup-reap-starting-gate","errorCode":null,"errorMessage":"shutdown stage gateway.startup_reap: starting gateway PID {gateway_pid} is {outcome:?}","messagePattern":"shutdown stage gateway\\.startup_reap: starting gateway PID (.+?) is (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/mcp/lifecycle.rs","lineNumber":618,"sourceCode":"    };\n    let Some(gateway_exe) = lease.gateway_exe.as_deref() else {\n        anyhow::bail!(\"shutdown stage gateway.startup_identity: lease has no executable\");\n    };\n    let current = read_gateway_startup_lease()?.ok_or_else(|| {\n        anyhow::anyhow!(\"shutdown stage gateway.startup_identity: lease disappeared\")\n    })?;\n    if current != *lease {\n        anyhow::bail!(\"shutdown stage gateway.startup_identity: startup generation changed\");\n    }\n\n    let outcome =\n        crate::commands::daemon_control::terminate_known(gateway_pid, Some(gateway_exe)).await;\n    if !matches!(\n        outcome,\n        crate::commands::daemon_control::KillOutcome::TermExited\n            | crate::commands::daemon_control::KillOutcome::KilledExited\n    ) {\n        anyhow::bail!(\n            \"shutdown stage gateway.startup_reap: starting gateway PID {gateway_pid} is {outcome:?}\"\n        );\n    }\n    Ok(())\n}\n\nasync fn clean_unowned_gateway_startup(lifecycle: &GatewayLifecycleLock) -> Result<()> {\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::Absent => {},\n        astrid_core::local_transport::ConnectOutcome::Stale => {\n            astrid_core::local_transport::remove_stale_endpoint(&socket)\n                .context(\"shutdown stage gateway.stale_listener_cleanup\")?;\n        },\n        astrid_core::local_transport::ConnectOutcome::Connected(_) => anyhow::bail!(","sourceCodeStart":600,"sourceCodeEnd":636,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/mcp/lifecycle.rs#L600-L636","documentation":"After sending SIGTERM/SIGKILL via daemon_control::terminate_known, the outcome was neither TermExited nor KilledExited — the starting gateway process did not confirm exit. The code reports the actual KillOutcome and bails.","triggerScenarios":"terminate_known returns outcomes like AlreadyDead mismatch, PermissionDenied, or the process survived the kill signal, when stop_gateway invokes stop_startup_gateway on the startup lease's PID.","commonSituations":"Gateway hung ignoring SIGTERM and SIGKILL delivery raced; PID reused by another process failing the exe identity check; insufficient permissions to signal the process (different user/container).","solutions":["Check whether the PID is still alive and kill it manually, then remove gateway marker/lease files","Re-run the shutdown after a short delay if the kill was racing process exit","Run under the same user that started the gateway, or fix permissions in containers","Inspect the reported outcome variant to determine the exact reap failure"],"exampleFix":"// before\nstop_gateway().await?;\n// after: handle non-exit reap outcomes\nif let Err(e) = stop_gateway().await {\n    if e.to_string().contains(\"gateway.startup_reap\") {\n        // remove stale markers manually once the process is confirmed dead\n        remove_gateway_startup_lease(None)?;\n    } else { return Err(e); }\n}","handlingStrategy":"retry","validationCode":"if !daemon_control::is_process_alive(gateway_pid) {\n    // already gone; just clear markers instead of terminating\n    remove_gateway_startup_lease(None)?;\n    return Ok(());\n}","typeGuard":null,"tryCatchPattern":"match stop_gateway().await {\n    Err(e) if e.to_string().contains(\"gateway.startup_reap\") => {\n        tokio::time::sleep(Duration::from_millis(500)).await;\n        remove_gateway_startup_lease(None)?; // finish cleanup manually\n    },\n    other => other?,\n}","preventionTips":["Run stop under the same user that started the gateway","Avoid killing the gateway externally before calling stop","Allow a grace period between failed kills and retries"],"tags":["process-management","signal","shutdown"],"backgroundTag":"process-termination-failed","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"}