astrid-runtime/astrid · error
timed out waiting for capsule command result
Error message
timed out waiting for capsule command result
What it means
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.
Source
Thrown at crates/astrid-cli/src/commands/capsule_verb.rs:296
enum CommandWait {
Result(serde_json::Value),
ProviderUnloaded,
}
async fn wait_for_command_result(
client: &mut SocketClient,
result_topic: &str,
provider: &str,
principal: &str,
timeout: Duration,
) -> Result<CommandWait> {
let deadline = tokio::time::Instant::now()
.checked_add(timeout)
.unwrap_or_else(tokio::time::Instant::now);
loop {
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
if remaining.is_zero() {
anyhow::bail!("timed out waiting for capsule command result");
}
let read = tokio::time::timeout(remaining, client.read_raw_frame()).await;
let frame = match read {
Ok(Ok(Some(bytes))) => bytes,
Ok(Ok(None)) => anyhow::bail!("daemon connection closed before command result"),
Ok(Err(err)) => return Err(err),
Err(_) => anyhow::bail!("timed out waiting for capsule command result"),
};
let Ok(raw) = serde_json::from_slice::<serde_json::Value>(&frame) else {
continue;
};
let topic = raw.get("topic").and_then(serde_json::Value::as_str);
if topic == Some(result_topic) {
return Ok(CommandWait::Result(raw));
}
if topic == Some(CAPSULES_LOADED_TOPIC)
&& capsules_loaded_missing_provider(&raw, provider, principal)View on GitHub (pinned to affd8760f4)
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.
Example fix
// before const RESULT_TIMEOUT: Duration = Duration::from_secs(30); // after: allow slow capsules more headroom const RESULT_TIMEOUT: Duration = Duration::from_secs(120);
Defensive patterns
Strategy: retry
Validate before calling
// Pre-check the provider capsule is loaded before sending a long-running command
fn capsule_loaded(capsules_loaded_raw: &serde_json::Value, provider: &str, principal: &str) -> bool {
!capsules_loaded_missing_provider(capsules_loaded_raw, provider, principal)
} Try / catch
match wait_for_command_result(&mut client, &topic, provider, principal, RESULT_TIMEOUT).await {
Ok(CommandWait::Result(raw)) => render_result(provider, &raw),
Ok(CommandWait::ProviderUnloaded) => eprintln!("capsule unloaded"),
Err(e) if e.to_string().contains("timed out") => {
// one retry with a doubled budget
wait_for_command_result(&mut client, &topic, provider, principal, RESULT_TIMEOUT * 2).await
},
Err(e) => return Err(e.into()),
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- candidate generation for '{id}' did not activate within {}ms
- running daemon returned unknown unload status {other:?}
- an Astrid daemon appears to be running but its uplink is unr
- candidate generation for '{id}' did not signal ready within
- Admin request timed out after {:?} waiting for {want_respons
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/f8eb586697ad6f2e.
Report an issue: GitHub.