{"record":{"id":"f8eb586697ad6f2e","repo":"astrid-runtime/astrid","slug":"timed-out-waiting-for-capsule-command-result","errorCode":null,"errorMessage":"timed out waiting for capsule command result","messagePattern":"timed out waiting for capsule command result","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/capsule_verb.rs","lineNumber":296,"sourceCode":"enum CommandWait {\n    Result(serde_json::Value),\n    ProviderUnloaded,\n}\n\nasync fn wait_for_command_result(\n    client: &mut SocketClient,\n    result_topic: &str,\n    provider: &str,\n    principal: &str,\n    timeout: Duration,\n) -> Result<CommandWait> {\n    let deadline = tokio::time::Instant::now()\n        .checked_add(timeout)\n        .unwrap_or_else(tokio::time::Instant::now);\n    loop {\n        let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());\n        if remaining.is_zero() {\n            anyhow::bail!(\"timed out waiting for capsule command result\");\n        }\n\n        let read = tokio::time::timeout(remaining, client.read_raw_frame()).await;\n        let frame = match read {\n            Ok(Ok(Some(bytes))) => bytes,\n            Ok(Ok(None)) => anyhow::bail!(\"daemon connection closed before command result\"),\n            Ok(Err(err)) => return Err(err),\n            Err(_) => anyhow::bail!(\"timed out waiting for capsule command result\"),\n        };\n        let Ok(raw) = serde_json::from_slice::<serde_json::Value>(&frame) else {\n            continue;\n        };\n        let topic = raw.get(\"topic\").and_then(serde_json::Value::as_str);\n        if topic == Some(result_topic) {\n            return Ok(CommandWait::Result(raw));\n        }\n        if topic == Some(CAPSULES_LOADED_TOPIC)\n            && capsules_loaded_missing_provider(&raw, provider, principal)","sourceCodeStart":278,"sourceCodeEnd":314,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/capsule_verb.rs#L278-L314","documentation":"wait_for_command_result polls the IPC socket for a frame whose topic matches the per-request result topic (cli_command_result:<req_id>) until a deadline expires. This error is raised at the top of the loop when the entire timeout budget has been consumed before the matching result frame arrived. The CLI turns it into 'Capsule <provider> did not respond within Ns.' for the user.","triggerScenarios":"Running a capsule verb whose provider never publishes the expected result topic within RESULT_TIMEOUT — e.g. a hung or slow capsule, a daemon busy-looping without forwarding the result, or a req_id mismatch so the result frame never matches result_topic.","commonSituations":"Capsule performs a long network call exceeding the timeout; daemon under heavy load starves the capsule runtime; a bug in a provider sends its result under a different req_id so the waiter spins until deadline.","solutions":["Retry the command; transient slowness often resolves on a second attempt.","Increase the result timeout (RESULT_TIMEOUT / RESULT_TIMEOUT_SECS) if the capsule legitimately needs longer, and rebuild the CLI.","Check `astrid status` and daemon logs to see whether the capsule/provider is alive and processing the request.","If the capsule is genuinely hung, restart the daemon (`astrid restart`) to clear the stuck capsule runtime."],"exampleFix":"// before\nconst RESULT_TIMEOUT: Duration = Duration::from_secs(30);\n// after: allow slow capsules more headroom\nconst RESULT_TIMEOUT: Duration = Duration::from_secs(120);","handlingStrategy":"retry","validationCode":"// Pre-check the provider capsule is loaded before sending a long-running command\nfn capsule_loaded(capsules_loaded_raw: &serde_json::Value, provider: &str, principal: &str) -> bool {\n    !capsules_loaded_missing_provider(capsules_loaded_raw, provider, principal)\n}","typeGuard":null,"tryCatchPattern":"match wait_for_command_result(&mut client, &topic, provider, principal, RESULT_TIMEOUT).await {\n    Ok(CommandWait::Result(raw)) => render_result(provider, &raw),\n    Ok(CommandWait::ProviderUnloaded) => eprintln!(\"capsule unloaded\"),\n    Err(e) if e.to_string().contains(\"timed out\") => {\n        // one retry with a doubled budget\n        wait_for_command_result(&mut client, &topic, provider, principal, RESULT_TIMEOUT * 2).await\n    },\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Size RESULT_TIMEOUT to the slowest legitimate capsule workload.","Add progress/heartbeat frames from long-running capsules so the wait can distinguish slow from hung.","Monitor daemon load; timeouts cluster when the kernel event loop is starved.","Fail fast on known-hung capsules by checking provider health before dispatching."],"tags":["timeout","ipc","capsule","async"],"backgroundTag":"request-timeout","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"}