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
- Inspect the wrapped error `{e}` in the chain — it carries the actual interceptor failure
- Update/rebuild the capsule so its tool_describe interceptor works or correctly signals 'unsupported' instead of erroring
- Check runtime/version compatibility between host and capsule (unsupported-signal detection may be stale)
- 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
- Keep host and capsule runtime versions aligned so 'unsupported' signals are recognized
- Test capsules' tool_describe interceptor in CI before release
- Handle Ok(None) explicitly: an unavailable interceptor means UNKNOWN tools, never inject []
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
- WASM capsule has no BLAKE3 hash in meta.json
- daemon closed guard uplink
- guard uplink authenticated as anonymous instead of requested
- unexpected response shape: {other:?}
- unexpected response from kernel: {other:?}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/e749b15c030cc3a9.
Report an issue: GitHub.