astrid-runtime/astrid · error · GatewayError::Internal

unexpected response shape for GetCapsuleMetadata: {other:?}

Error message

unexpected response shape for GetCapsuleMetadata: {other:?}

What it means

In capsule_metadata_for, when the daemon's response to GetCapsuleMetadata is neither Success nor a Denied probe message, the gateway treats it as an unrecognized protocol variant and surfaces it as an Internal error. This guards against silently mishandling new or corrupted daemon replies. The debug-formatted value is embedded in the message for diagnosis.

Source

Thrown at crates/astrid-gateway/src/routes/env.rs:266

    caller: &crate::auth::CallerContext,
) -> GatewayResult<Vec<CapsuleMetadataEntry>> {
    let client = state.kernel_client_for(caller)?;
    let resp = client
        .request(KernelRequest::GetCapsuleMetadata)
        .await
        .map_err(daemon_kernel_error)?;
    match resp {
        KernelResponse::CapsuleMetadata(entries) => Ok(entries),
        KernelResponse::Error(msg) => {
            tracing::warn!(
                security_event = true,
                principal = %caller.principal,
                reason = %msg,
                "capsule env visibility probe denied; returning hidden not-found"
            );
            Err(GatewayError::NotFound)
        },
        other => Err(GatewayError::Internal(anyhow::anyhow!(
            "unexpected response shape for GetCapsuleMetadata: {other:?}"
        ))),
    }
}

fn metadata_entry(
    entries: &[CapsuleMetadataEntry],
    capsule_id: &str,
    caller: &str,
) -> GatewayResult<CapsuleMetadataEntry> {
    entries
        .iter()
        .find(|entry| entry.name == capsule_id)
        .cloned()
        .ok_or_else(|| {
            tracing::debug!(
                security_event = true,
                principal = %caller,

View on GitHub (pinned to affd8760f4)

Solutions

  1. Upgrade or downgrade the gateway and daemon to matching versions so AdminResponseBody variants agree
  2. Log the full `other` payload and add a match arm handling the unexpected variant (typically AdminResponseBody::Error should map to Forbidden/NotFound, not Internal)
  3. Check the daemon logs for the original GetCapsuleMetadata failure and fix the root cause there

Example fix

// before
other => Err(GatewayError::Internal(anyhow::anyhow!(
    "unexpected response shape for GetCapsuleMetadata: {other:?}"
))),
// after
AdminResponseBody::Error(msg) => Err(GatewayError::Forbidden { reason: msg }),
other => Err(GatewayError::Internal(anyhow::anyhow!(
    "unexpected response shape for GetCapsuleMetadata: {other:?}"
))),
Defensive patterns

Strategy: try-catch

Validate before calling

// Before probing, confirm gateway/daemon protocol versions match
let daemon_version = client.version().await?;
assert_eq!(daemon_version, GATEWAY_PROTOCOL_VERSION, "gateway/daemon protocol skew");

Type guard

fn is_expected_metadata_response(resp: &AdminResponseBody) -> bool {
    matches!(resp, AdminResponseBody::Success(_) | AdminResponseBody::Error(_))
}

Try / catch

match capsule_metadata_for(state, &caller, &capsule_id).await {
    Ok(schema) => schema,
    Err(GatewayError::NotFound) => return hidden_not_found(),
    Err(GatewayError::Internal(e)) if e.to_string().contains("unexpected response shape") => {
        tracing::error!(%e, "protocol skew between gateway and daemon");
        return upgrade_hint_response();
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: The kernel/daemon returns an AdminResponseBody variant (or message shape) that capsule_metadata_for's match arms do not cover when probing capsule visibility via GetCapsuleMetadata — e.g. an Error body, a newer protocol variant, or a deserialization drift between gateway and daemon versions.

Common situations: Gateway and daemon built from different versions where AdminResponseBody gained a new variant; a daemon bug returning an Error body instead of Denied; test doubles emitting stub responses that don't match the expected shapes.

Related errors


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