{"record":{"id":"313542a759839983","repo":"astrid-runtime/astrid","slug":"shutdown-stage-daemon-shutdown-ack-rejected-rea","errorCode":null,"errorMessage":"shutdown stage daemon.shutdown_ack: rejected: {reason}","messagePattern":"shutdown stage daemon\\.shutdown_ack: rejected: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/daemon.rs","lineNumber":771,"sourceCode":"    // Graceful path: the socket is present and serviceable.\n    // Deliberately bypass the selected-workspace check: stopping a daemon is\n    // the recovery path when that daemon belongs to another project/layout.\n    if socket_present && let Ok(client) = socket_client::connect_kernel_for_recovery().await {\n        let mut client = client.with_timeout(Duration::from_secs(10));\n        let response = client\n            .request(KernelRequest::Shutdown {\n                reason: Some(\"astrid stop\".to_string()),\n            })\n            .await\n            .context(\"shutdown stage daemon.shutdown_ack\")?;\n        let disposition = match response {\n            KernelResponse::Success(_) => {\n                // ACK only — confirm the process actually exits before\n                // declaring success, and escalate if it wedged.\n                confirm_graceful_stop(recorded, &socket_path, &pid_path).await?\n            },\n            KernelResponse::Error(reason) => {\n                anyhow::bail!(\"shutdown stage daemon.shutdown_ack: rejected: {reason}\")\n            },\n            other => {\n                anyhow::bail!(\"shutdown stage daemon.shutdown_ack: unexpected response: {other:?}\")\n            },\n        };\n        cleanup_daemon_runtime(&socket_path, &pid_path).await?;\n        return Ok(disposition);\n    }\n\n    // Orphan path: the socket is present but unreachable (hung/half-dead\n    // daemon), OR the socket is already gone but a live recorded daemon is still\n    // holding the lock. A clean shutdown request is impossible either way, so\n    // signal the recorded PID (identity-gated) and clean up. Using the PID we\n    // captured up front — not a re-read — closes the window where the daemon\n    // deletes its own PID file mid-wedge.\n    let outcome = match recorded.as_ref() {\n        Some(identity) => daemon_control::terminate_identity(identity, &pid_path).await,\n        None => daemon_control::KillOutcome::NotRunning,","sourceCodeStart":753,"sourceCodeEnd":789,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/daemon.rs#L753-L789","documentation":"When `astrid stop` sends KernelRequest::Shutdown over the socket, the daemon should ACK with KernelResponse::Success. If it instead replies KernelResponse::Error(reason), the daemon refused to begin shutting down, and the CLI bails with the staged message naming the rejection reason so the user knows shutdown never started.","triggerScenarios":"stop_daemon's graceful path: the socket is present, connect_kernel_for_recovery succeeds, the Shutdown request is delivered, and the daemon responds with KernelResponse::Error(reason) instead of Success — e.g. it believes shutdown is invalid in its current state or an internal precondition failed.","commonSituations":"Daemon mid-startup or mid-capsule-load refusing shutdown; daemon with active sessions/leases it considers non-interruptible; daemon in a degraded state whose shutdown handler returned a validation error; version skew where the daemon doesn't recognize the request payload.","solutions":["Read the {reason} in the message — it states why the daemon refused.","Retry `astrid stop` once the transient condition (load/lease) clears.","If shutdown is persistently rejected, use `astrid restart`, which force-terminates via the identity-gated signal path.","Check daemon logs for the handler that produced the rejection."],"exampleFix":"// before\n$ astrid stop\nError: shutdown stage daemon.shutdown_ack: rejected: capsules still loading\n// after\n$ sleep 5 && astrid stop   # or: astrid restart to force-terminate","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"fn is_shutdown_ack(resp: &KernelResponse) -> bool {\n    matches!(resp, KernelResponse::Success(_))\n}","tryCatchPattern":"match stop_daemon().await {\n    Err(e) if e.to_string().contains(\"daemon.shutdown_ack: rejected\") => {\n        eprintln!(\"daemon refused shutdown: {e}; falling back to restart\");\n        run(vec![\"astrid\", \"restart\"])?;\n    }\n    r => r?,\n}","preventionTips":["Don't stop while capsules are loading or long tasks run; wait for idle first.","Read the embedded rejection reason before escalating to forced restart.","Keep daemon version aligned with CLI to avoid payload-validation rejections.","If rejections repeat, inspect daemon logs for the shutdown handler's precondition."],"tags":["daemon","shutdown","ipc","rpc"],"backgroundTag":"api-error-response","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"}