{"record":{"id":"0034a6240fd4343c","repo":"astrid-runtime/astrid","slug":"unexpected-response-shape-for-getcapsulemetadata","errorCode":null,"errorMessage":"unexpected response shape for GetCapsuleMetadata: {other:?}","messagePattern":"unexpected response shape for GetCapsuleMetadata: (.+?)","errorType":"http","errorClass":"GatewayError::Internal","httpStatus":500,"severity":"error","filePath":"crates/astrid-gateway/src/routes/env.rs","lineNumber":266,"sourceCode":"    caller: &crate::auth::CallerContext,\n) -> GatewayResult<Vec<CapsuleMetadataEntry>> {\n    let client = state.kernel_client_for(caller)?;\n    let resp = client\n        .request(KernelRequest::GetCapsuleMetadata)\n        .await\n        .map_err(daemon_kernel_error)?;\n    match resp {\n        KernelResponse::CapsuleMetadata(entries) => Ok(entries),\n        KernelResponse::Error(msg) => {\n            tracing::warn!(\n                security_event = true,\n                principal = %caller.principal,\n                reason = %msg,\n                \"capsule env visibility probe denied; returning hidden not-found\"\n            );\n            Err(GatewayError::NotFound)\n        },\n        other => Err(GatewayError::Internal(anyhow::anyhow!(\n            \"unexpected response shape for GetCapsuleMetadata: {other:?}\"\n        ))),\n    }\n}\n\nfn metadata_entry(\n    entries: &[CapsuleMetadataEntry],\n    capsule_id: &str,\n    caller: &str,\n) -> GatewayResult<CapsuleMetadataEntry> {\n    entries\n        .iter()\n        .find(|entry| entry.name == capsule_id)\n        .cloned()\n        .ok_or_else(|| {\n            tracing::debug!(\n                security_event = true,\n                principal = %caller,","sourceCodeStart":248,"sourceCodeEnd":284,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-gateway/src/routes/env.rs#L248-L284","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Upgrade or downgrade the gateway and daemon to matching versions so AdminResponseBody variants agree","Log the full `other` payload and add a match arm handling the unexpected variant (typically AdminResponseBody::Error should map to Forbidden/NotFound, not Internal)","Check the daemon logs for the original GetCapsuleMetadata failure and fix the root cause there"],"exampleFix":"// before\nother => Err(GatewayError::Internal(anyhow::anyhow!(\n    \"unexpected response shape for GetCapsuleMetadata: {other:?}\"\n))),\n// after\nAdminResponseBody::Error(msg) => Err(GatewayError::Forbidden { reason: msg }),\nother => Err(GatewayError::Internal(anyhow::anyhow!(\n    \"unexpected response shape for GetCapsuleMetadata: {other:?}\"\n))),","handlingStrategy":"try-catch","validationCode":"// Before probing, confirm gateway/daemon protocol versions match\nlet daemon_version = client.version().await?;\nassert_eq!(daemon_version, GATEWAY_PROTOCOL_VERSION, \"gateway/daemon protocol skew\");","typeGuard":"fn is_expected_metadata_response(resp: &AdminResponseBody) -> bool {\n    matches!(resp, AdminResponseBody::Success(_) | AdminResponseBody::Error(_))\n}","tryCatchPattern":"match capsule_metadata_for(state, &caller, &capsule_id).await {\n    Ok(schema) => schema,\n    Err(GatewayError::NotFound) => return hidden_not_found(),\n    Err(GatewayError::Internal(e)) if e.to_string().contains(\"unexpected response shape\") => {\n        tracing::error!(%e, \"protocol skew between gateway and daemon\");\n        return upgrade_hint_response();\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Keep gateway and daemon built from the same commit/version","Use exhaustive match with #[non_exhaustive] awareness so new variants are caught at compile time","Add integration tests covering every AdminResponseBody variant for each request kind","Log the raw daemon payload before mapping to Internal errors"],"tags":["rust","internal-error","response-shape","gateway","daemon"],"backgroundTag":"unexpected-response-shape","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}