{"record":{"id":"badc786633e79f68","repo":"astrid-runtime/astrid","slug":"daemon-closed-guard-uplink","errorCode":null,"errorMessage":"daemon closed guard uplink","messagePattern":"daemon closed guard uplink","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/mcp/session_guard.rs","lineNumber":54,"sourceCode":"    principal: &astrid_core::PrincipalId,\n    daemon_root: &Path,\n) -> Result<std::convert::Infallible> {\n    daemon::ensure_daemon_quiet(\"mcp-session-guard\", Some(daemon_root)).await?;\n\n    let session = astrid_core::SessionId::from_uuid(Uuid::new_v4());\n    let c =\n        crate::socket_client::connect_for_workspace(session, principal.clone(), Some(daemon_root))\n            .await\n            .context(\"failed to connect guard uplink to daemon\")?;\n\n    validate_guard_auth(principal, c.is_authenticated())?;\n\n    debug!(%principal, \"MCP session guard: daemon uplink established\");\n    let mut client = c;\n    loop {\n        match client.read_raw_frame().await {\n            Ok(Some(_)) => {},\n            Ok(None) => anyhow::bail!(\"daemon closed guard uplink\"),\n            Err(e) => return Err(e).context(\"guard uplink read failed\"),\n        }\n    }\n}\n\nfn validate_guard_auth(principal: &astrid_core::PrincipalId, authenticated: bool) -> Result<()> {\n    if authenticated || *principal == astrid_core::PrincipalId::anonymous() {\n        return Ok(());\n    }\n\n    anyhow::bail!(\n        \"guard uplink authenticated as anonymous instead of requested principal '{principal}'\"\n    )\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/mcp/session_guard.rs#L36-L72","documentation":"hold_guard_uplink keeps the MCP session guard's uplink alive by reading raw frames from the daemon client in a loop. When read_raw_frame returns Ok(None) the daemon has closed the connection (EOF), and the guard bails with this error because an active guard cannot persist without the daemon uplink. It exists to surface daemon-side disconnects rather than silently ending the guard session.","triggerScenarios":"The daemon process shuts down or crashes while the session guard is holding the uplink; the daemon explicitly closes the guard connection (e.g. session revoked, shutdown of the guard service); network/socket drop producing a clean EOF (Ok(None)) rather than an Err.","commonSituations":"Daemon restarted for an upgrade while a guard was active; system shutdown or OOM kill of the daemon; daemon closing the guard because the session expired or was revoked; socket cleanup by an init/supervisor.","solutions":["Check daemon logs for why the guard uplink was closed (shutdown, revocation, crash) at the matching time.","Restart the daemon and re-establish the session guard.","Make the guard resilient: on Ok(None), reconnect to the daemon and re-run the guard handshake instead of bailing.","Run the daemon under a supervisor (systemd) so it restarts and the guard can re-acquire the uplink."],"exampleFix":"// before\nOk(None) => anyhow::bail!(\"daemon closed guard uplink\"),\n// after\nOk(None) => {\n    // reconnect with backoff instead of failing the guard session\n    client = reconnect_guard_uplink(principal).await?;\n    continue;\n}","handlingStrategy":"try-catch","validationCode":"// verify daemon liveness before holding the guard uplink\nif !is_daemon_alive().await {\n    anyhow::bail!(\"daemon not running; guard uplink will close immediately\");\n}","typeGuard":null,"tryCatchPattern":"match hold_guard_uplink(client, principal).await {\n    Ok(()) => {},\n    Err(e) if e.to_string().contains(\"daemon closed guard uplink\") => {\n        // daemon went away: restart/reconnect and re-establish the guard\n        restart_daemon_and_reacquire_guard(principal).await?;\n    },\n    Err(e) => return Err(e),\n}","preventionTips":["Run the daemon under a supervisor (systemd/supervisord) so uplinks survive crashes and upgrades.","Avoid restarting the daemon while session guards are active; drain guards first.","Distinguish clean EOF (Ok(None)) from read errors in logs to tell intentional close from failure.","Implement reconnect-with-backoff in the guard loop rather than treating any disconnect as fatal."],"tags":["daemon","ipc","connection-closed","mcp","rust"],"backgroundTag":"broken-pipe","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"}