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

  1. Retry the command; transient slowness often resolves on a second attempt.
  2. Increase the result timeout (RESULT_TIMEOUT / RESULT_TIMEOUT_SECS) if the capsule legitimately needs longer, and rebuild the CLI.
  3. Check `astrid status` and daemon logs to see whether the capsule/provider is alive and processing the request.
  4. 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

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.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/f8eb586697ad6f2e. Report an issue: GitHub.