{"record":{"id":"a71cf6dbfa6fb7d0","repo":"astrid-runtime/astrid","slug":"primary-additional-shutdown-failure-seconda","errorCode":null,"errorMessage":"{primary:#}; additional shutdown failure: {secondary:#}","messagePattern":"(.+?); additional shutdown failure: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/daemon.rs","lineNumber":728,"sourceCode":"    println!(\"{}\", theme::Theme::success(message));\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum DaemonStopDisposition {\n    AlreadyStopped,\n    Graceful,\n    Forced,\n}\n\nfn combine_stop_results(\n    gateway: Result<()>,\n    daemon: Result<DaemonStopDisposition>,\n) -> Result<DaemonStopDisposition> {\n    match (gateway, daemon) {\n        (Ok(()), Ok(disposition)) => Ok(disposition),\n        (Err(primary), Ok(_)) | (Ok(()), Err(primary)) => Err(primary),\n        (Err(primary), Err(secondary)) => {\n            anyhow::bail!(\"{primary:#}; additional shutdown failure: {secondary:#}\")\n        },\n    }\n}\n\nasync fn stop_daemon() -> Result<DaemonStopDisposition> {\n    let socket_path = socket_client::proxy_socket_path();\n    let pid_path = socket_client::pid_path();\n\n    // Capture the daemon's identity up front: it deletes its own PID file only\n    // on a CLEAN exit, so reading it before shutdown is the only reliable way to\n    // keep a handle for confirming exit / signalling a wedged shutdown.\n    let recorded = daemon_control::read_daemon_identity(&pid_path);\n    let socket_present = astrid_core::local_transport::endpoint_is_present(&socket_path)\n        .context(\"failed to inspect daemon endpoint\")?;\n\n    // Genuinely nothing running: no socket AND no live recorded process.\n    let recorded_alive = recorded\n        .as_ref()","sourceCodeStart":710,"sourceCodeEnd":746,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/daemon.rs#L710-L746","documentation":"combine_stop_results merges the two halves of `astrid stop` — the MCP gateway shutdown and the daemon shutdown. When BOTH fail, neither failure can be silently dropped, so the primary is reported and the daemon-side failure is appended as 'additional shutdown failure: {secondary:#}' via anyhow's alternate formatting (the full error chain).","triggerScenarios":"`astrid stop` (handle_stop) where stop_gateway() and stop_daemon() both return Err — e.g. the socket is unreachable AND the gateway process also fails to shut down (wedged child, permission problem, both holding stale locks).","commonSituations":"System under resource pressure so both processes are wedged; shared runtime-dir permission problems breaking both shutdown paths; a kill sweep (OOM, container stop) that left both components half-dead; stale PID files confusing both paths.","solutions":["Read the primary error first — fix that root cause; the secondary is usually the same underlying problem (e.g. permissions, wedged PID).","If both report a stuck PID, SIGTERM/SIGKILL the recorded daemon PID manually and remove stale runtime files.","Re-run `astrid stop` after cleanup; it is idempotent (AlreadyStopped path).","Check runtime dir ownership/permissions if both failures mention file access."],"exampleFix":"// before\nError: gateway stop failed: ...; additional shutdown failure: shutdown stage daemon.shutdown_ack: rejected: ...\n// after\n$ kill -TERM <recorded-daemon-pid> && rm -f ~/.astrid/run/system.sock ~/.astrid/run/daemon.pid\n$ astrid stop","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"null","tryCatchPattern":"match stop_everything().await {\n    Err(e) if e.to_string().contains(\"additional shutdown failure\") => {\n        // both legs failed: force-clean the recorded identity then retry once\n        force_clean_runtime()?;\n        stop_everything().await?;\n    }\n    r => r?,\n}","preventionTips":["Treat the primary error as the root cause; the appended failure usually shares it.","Run stop under the same AX_HOME/env as the daemon so both shutdown paths resolve the same run-dir.","Avoid killing both gateway and daemon with external signals right before `astrid stop`.","Verify runtime-dir permissions so neither leg fails on file access."],"tags":["daemon","shutdown","cli","error-chaining"],"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"}