{"record":{"id":"f568676771d96d17","repo":"astrid-runtime/astrid","slug":"daemon-connection-closed-while-waiting-for-the-mcp","errorCode":null,"errorMessage":"daemon connection closed while waiting for the MCP broker for principal '{principal}'","messagePattern":"daemon connection closed while waiting for the MCP broker for principal '(.+?)'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/mcp/readiness.rs","lineNumber":119,"sourceCode":"            anyhow::bail!(\n                \"MCP broker did not become ready for principal '{principal}' within {}s; no capsule answered {TOOLS_LIST_TOPIC}\",\n                ready_deadline.as_secs()\n            );\n        }\n        if now >= retry_at {\n            debug!(%principal, \"MCP broker readiness probe interval elapsed; retrying idempotent tools/list\");\n            send_probe(io, principal, &mut outstanding).await?;\n            retry_at = Instant::now()\n                .checked_add(retry_interval)\n                .unwrap_or(deadline);\n            continue;\n        }\n\n        let wake_at = retry_at.min(deadline);\n        let frame = match tokio::time::timeout_at(wake_at, io.read_raw_frame()).await {\n            Ok(Ok(Some(frame))) => frame,\n            Ok(Ok(None)) => {\n                anyhow::bail!(\n                    \"daemon connection closed while waiting for the MCP broker for principal '{principal}'\"\n                )\n            },\n            Ok(Err(error)) => {\n                return Err(error).with_context(|| {\n                    format!(\n                        \"daemon connection failed while waiting for the MCP broker for principal '{principal}'\"\n                    )\n                });\n            },\n            Err(_) => {\n                if Instant::now() >= deadline {\n                    anyhow::bail!(\n                        \"MCP broker did not become ready for principal '{principal}' within {}s; no capsule answered {TOOLS_LIST_TOPIC}\",\n                        ready_deadline.as_secs()\n                    );\n                }\n                debug!(%principal, \"MCP broker readiness probe unanswered; retrying idempotent tools/list\");","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/mcp/readiness.rs#L101-L137","documentation":"While waiting for the MCP broker, wait_for_broker_on reads frames from the daemon connection with a timeout. If the read returns None (EOF) — the daemon closed the connection before the broker answered — the loop aborts with this error instead of retrying forever on a dead socket.","triggerScenarios":"During `astrid mcp ready` / wait_for_broker: the daemon process exits or drops the connection between probes, so read_raw_frame() yields Ok(None) before the deadline.","commonSituations":"Daemon crash or restart mid-readiness-check; daemon killed by OOM or an admin; connection dropped by an intermediary; connecting to a socket whose daemon just shut down after a stop command.","solutions":["Restart the daemon (`astrid daemon start` or equivalent) and re-run the readiness check.","Retry the readiness command — this is typically transient if the daemon was restarting.","Inspect daemon logs/journal for crash reasons (panic, OOM) and fix the underlying failure.","Ensure nothing concurrently stops the gateway/daemon (e.g. a stray `astrid mcp stop` in scripts) during readiness waits."],"exampleFix":"// before\nlet frame = match tokio::time::timeout_at(wake_at, io.read_raw_frame()).await {\n    Ok(Ok(None)) => bail!(\"daemon connection closed...\"),\n    ...\n// after (caller-side retry)\nfor attempt in 0..3 {\n    match wait_for_broker(&mut io, principal, deadline).await {\n        Ok(()) => break,\n        Err(e) if attempt < 2 => { tokio::time::sleep(Duration::from_secs(1)).await; }\n        Err(e) => return Err(e),\n    }\n}","handlingStrategy":"retry","validationCode":"// probe the daemon liveness before waiting on the broker\nif astrid_core::local_transport::connect_outcome(&daemon_socket).await\n    != ConnectOutcome::Connected { eprintln!(\"daemon not running; start it first\"); }","typeGuard":null,"tryCatchPattern":"match wait_for_broker(&mut io, principal, timeout).await {\n    Err(e) if e.to_string().contains(\"daemon connection closed\") => {\n        tokio::time::sleep(Duration::from_secs(1)).await;\n        // reconnect to the daemon and retry the readiness wait (bounded retries)\n    }\n    other => other?,\n}","preventionTips":["Confirm the daemon is up and stable before readiness waits","Prevent concurrent stop/restart scripts from dropping the daemon mid-check","Watch daemon memory (OOM) and crash logs","Use bounded reconnect-and-retry loops around readiness checks"],"tags":["mcp","connection","daemon","eof"],"backgroundTag":"connection-refused","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}