{"record":{"id":"65c0ab9b8d638a2c","repo":"astrid-runtime/astrid","slug":"shutdown-stage-daemon-process-reap-shutdown-was-a","errorCode":null,"errorMessage":"shutdown stage daemon.process_reap: shutdown was acknowledged but no recorded PID exists, so process exit cannot be verified (listener: {})","messagePattern":"shutdown stage daemon\\.process_reap: shutdown was acknowledged but no recorded PID exists, so process exit cannot be verified \\(listener: (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/daemon.rs","lineNumber":807,"sourceCode":"        None => daemon_control::KillOutcome::NotRunning,\n    };\n    let disposition = confirm_kill_outcome(outcome)?;\n    cleanup_daemon_runtime(&socket_path, &pid_path).await?;\n    Ok(disposition)\n}\n\n/// After a graceful shutdown ACK, confirm the daemon process actually exited —\n/// an ACK is \"shutting down\", not \"exited and released the lock\". Wait for the\n/// recorded PID to die; if it wedges past the grace window it is still holding\n/// the lock, so escalate through the same identity-gated signal path as an\n/// unreachable orphan. Runtime files are cleaned only once the process is gone.\nasync fn confirm_graceful_stop(\n    recorded: Option<daemon_control::DaemonIdentity>,\n    socket_path: &Path,\n    pid_path: &Path,\n) -> Result<DaemonStopDisposition> {\n    let Some(identity) = recorded else {\n        anyhow::bail!(\n            \"shutdown stage daemon.process_reap: shutdown was acknowledged but no recorded PID exists, so process exit cannot be verified (listener: {})\",\n            socket_path.display()\n        );\n    };\n\n    if daemon_control::wait_for_exit(identity.pid, daemon_control::GRACE).await {\n        return Ok(DaemonStopDisposition::Graceful);\n    }\n\n    // Acknowledged but still alive past the grace window → wedged mid-shutdown,\n    // still holding the lock. Escalate with a signal (identity-gated).\n    eprintln!(\n        \"{}\",\n        theme::Theme::warning(\n            \"Daemon acknowledged shutdown but is still running; escalating with a signal so the \\\n             state-db lock is released.\"\n        )\n    );","sourceCodeStart":789,"sourceCodeEnd":825,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/daemon.rs#L789-L825","documentation":"An ACK to Shutdown only means 'shutting down', not that the process exited and released the singleton lock. confirm_graceful_stop verifies exit via the PID recorded BEFORE the shutdown request. If no PID file existed at that time, exit cannot be verified, so the CLI refuses to claim success (and refuses to delete markers blindly), bailing with the staged daemon.process_reap message including the socket path.","triggerScenarios":"Graceful stop path where the daemon ACKs Shutdown but daemon_control::read_daemon_identity(&pid_path) returned None — the pid file was missing before the request was sent (daemon started without writing it, ran under a different home/AX_HOME, or markers were already deleted).","commonSituations":"Daemon spawned by a different/older build that didn't persist a PID file; AX_HOME or runtime-dir env differing between the daemon and CLI so the CLI reads the wrong pid path; someone manually deleted the pid file while the daemon still runs; container where the daemon runs in another namespace.","solutions":["Ensure the CLI and daemon share the same AX_HOME / runtime dir so pid_path resolves correctly.","Find the daemon process manually (pgrep astrid) and stop it, then remove stale socket/pid markers.","Re-run `astrid stop` — after markers are consistent, the recorded-identity path works.","Check daemon logs for why it started without persisting its PID file."],"exampleFix":"// before\nError: shutdown stage daemon.process_reap: shutdown was acknowledged but no recorded PID exists ...\n// after\n$ export AX_HOME=~/.astrid   # same home the daemon uses\n$ pgrep -f astrid-daemon && kill -TERM <pid>\n$ astrid stop","handlingStrategy":"validation","validationCode":"// verify a PID file exists (and is readable) before stopping\nlet pid_path = socket_client::pid_path();\nif !pid_path.try_exists()? {\n    eprintln!(\"no recorded PID at {}; stop cannot verify exit\", pid_path.display());\n}","typeGuard":"fn has_recorded_identity(pid_path: &std::path::Path) -> bool {\n    std::fs::read_to_string(pid_path).map(|s| !s.trim().is_empty()).unwrap_or(false)\n}","tryCatchPattern":"match stop_daemon().await {\n    Err(e) if e.to_string().contains(\"no recorded PID exists\") => {\n        eprintln!(\"ACK without PID file; locate daemon manually\");\n        manual_reap_and_clean()?;\n    }\n    r => r?,\n}","preventionTips":["Always start the daemon through `astrid start` so it persists its PID file.","Keep AX_HOME identical between the daemon's environment and any stop scripts.","Never delete daemon.pid while the daemon runs.","Use `astrid restart` instead of ad-hoc marker deletion to keep runtime files consistent."],"tags":["daemon","shutdown","pid","ipc"],"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-14T05:17:10.506Z"}