astrid-runtime/astrid · error

tool_describe interceptor failed: {e}

Error message

tool_describe interceptor failed: {e}

What it means

interpret_describe_result converts a failing tool_describe interceptor invocation into this anyhow error. The interceptor channel itself errored in a way that is NOT an 'unsupported' reply: Err(e) matching is_unsupported returns Ok(None) (tool surface unknown, fan-out allowed), but any other error is fatal and wrapped with this message.

Source

Thrown at crates/astrid-capsule/src/tool_discovery.rs:128

    )
    .with_principal(principal.to_string())
}

/// Pure mapping of a `tool_describe` interceptor outcome to a captured tool
/// surface. Split out from [`describe_loaded_capsule`] so the capture semantics
/// — especially the pool-less `NotSupported => None` case that fixes #1198 — are
/// unit-testable without a live capsule.
fn interpret_describe_result(
    outcome: Result<InterceptResult, crate::error::CapsuleError>,
) -> anyhow::Result<Option<Vec<ToolDescriptor>>> {
    let result = match outcome {
        Ok(r) => r,
        // Pool-less run-loop capsule: the interceptor path isn't available, so
        // the tool surface is UNKNOWN — not empty. Signal absent (`None`) so the
        // caller lets the describe fan-out supply it (#1198), rather than
        // injecting `[]` and suppressing the fan-out.
        Err(e) if is_unsupported(&e) => return Ok(None),
        Err(e) => return Err(anyhow::anyhow!("tool_describe interceptor failed: {e}")),
    };

    let payload = match result {
        InterceptResult::Continue(bytes) | InterceptResult::Final(bytes) => bytes,
        // A capsule that CAN run interceptors but has no `#[astrid::tool]` arm
        // (e.g. the broker) genuinely has zero static tools: captured-empty
        // (`Some([])`), NOT absent — there is nothing for a fan-out to supply.
        InterceptResult::Deny { reason } if is_unknown_action(&reason) => {
            return Ok(Some(Vec::new()));
        },
        // Any other deny is a genuine refusal — surface it.
        InterceptResult::Deny { reason } => {
            anyhow::bail!("tool_describe interceptor denied: {reason}");
        },
    };

    if payload.is_empty() {
        return Ok(Some(Vec::new()));

View on GitHub (pinned to affd8760f4)

Solutions

  1. Inspect the wrapped error `{e}` in the chain — it carries the actual interceptor failure
  2. Update/rebuild the capsule so its tool_describe interceptor works or correctly signals 'unsupported' instead of erroring
  3. Check runtime/version compatibility between host and capsule (unsupported-signal detection may be stale)
  4. If the capsule is pool-less and cannot run interceptors, ensure it returns the unsupported signal so Ok(None) is taken and the describe fan-out runs
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check: only call describe when the capsule supports interceptors
if !capsule.supports_interceptor("tool_describe") {
    return Ok(None); // let the describe fan-out supply the tool surface
}

Type guard

fn is_unsupported_err(e: &anyhow::Error) -> bool {
    e.chain().any(|c| is_unsupported(c))
}

Try / catch

match interpret_describe_result(&capsule) {
    Ok(None) => fan_out_describe(&capsule), // unsupported: tool surface unknown, not empty
    Ok(Some(tools)) => apply(tools),
    Err(e) if e.to_string().contains("interceptor failed") => {
        log::warn!("describe failed, falling back to fan-out: {e}");
        fan_out_describe(&capsule)
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: describe_loaded_capsule_status(_for) or the fan-out tests trigger interpret_describe_result; the capsule's tool_describe interceptor returns an error other than a not-supported/unsupported signal — e.g. the guest capsule panicked, the IPC/interceptor transport failed, or the guest returned a malformed error reply.

Common situations: Capsule running without the interceptor path but reporting a non-unsupported error; a broken or hostile capsule crashing during tool_describe; version mismatch where the guest's error reply is no longer recognized as 'unsupported'; transport/IPC failures between host and capsule runtime.

Related errors


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