{"record":{"id":"8dab2c8e7c3b2b8a","repo":"astrid-runtime/astrid","slug":"unexpected-response-shape-other-8dab2c","errorCode":null,"errorMessage":"unexpected response shape: {other:?}","messagePattern":"unexpected response shape: (.+?)","errorType":"http","errorClass":"GatewayError::Internal","httpStatus":500,"severity":"error","filePath":"crates/astrid-gateway/src/routes/invites.rs","lineNumber":97,"sourceCode":"    let bytes = axum::body::to_bytes(req.into_body(), 64 * 1024)\n        .await\n        .map_err(|e| GatewayError::BadRequest(format!(\"body read: {e}\")))?;\n    let body: IssueRequest = serde_json::from_slice(&bytes)?;\n\n    let client = state.admin_client_for(&caller)?;\n    let resp = client\n        .request(AdminRequestKind::InviteIssue {\n            group: body.group,\n            expires_secs: body.expires_secs,\n            max_uses: body.max_uses,\n            metadata: body.metadata,\n        })\n        .await\n        .map_err(|e| GatewayError::Internal(anyhow::anyhow!(\"daemon request: {e}\")))?;\n    match resp {\n        AdminResponseBody::Invite(invite) => Ok(Json(IssueResponse { invite })),\n        AdminResponseBody::Error(msg) => Err(GatewayError::Forbidden { reason: msg }),\n        other => Err(GatewayError::Internal(anyhow::anyhow!(\n            \"unexpected response shape: {other:?}\"\n        ))),\n    }\n}\n\n/// `OpenAPI` schema mirror of [`astrid_core::kernel_api::InviteSummary`].\n/// Never constructed; resolves the `value_type` on\n/// [`ListResponse::invites`] to a typed schema. Keep it\n/// field-for-field with the serialized shape of `InviteSummary` —\n/// note the field is `token_fingerprint` (not `fingerprint`), and\n/// `issued_at_epoch` is always present.\n#[derive(ToSchema)]\npub struct InviteSummaryView {\n    /// Domain-separated `blake3:<hex>` fingerprint of the token. Raw tokens are never\n    /// leaked through list responses.\n    pub token_fingerprint: String,\n    /// Group the redeemer will join.\n    pub group: String,","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-gateway/src/routes/invites.rs#L79-L115","documentation":"After a successful daemon round-trip, issue_invite expects an AdminResponseBody::Invite; a daemon-reported error becomes Forbidden, but any other body variant is an unrecognized protocol shape and is raised as an Internal error. This catches version skew or daemon bugs where the reply doesn't match the request kind.","triggerScenarios":"The daemon responds to InviteIssue with a body other than Invite or Error — e.g. InviteList, a new enum variant added in a newer daemon, or a corrupted/deserialized-wrong payload.","commonSituations":"Gateway and daemon at different versions; a daemon that mis-routes the reply for the request kind; test doubles returning the wrong AdminResponseBody variant.","solutions":["Run gateway and daemon from matching builds so AdminResponseBody variants agree","Check daemon logs for why the reply body didn't match the InviteIssue request","Extend the match to handle the unexpected variant explicitly if it's a legitimate new response"],"exampleFix":"// before\nother => Err(GatewayError::Internal(anyhow::anyhow!(\n    \"unexpected response shape: {other:?}\"\n))),\n// after\nAdminResponseBody::Invite(invite) => Ok(Json(IssueResponse { invite })),\nAdminResponseBody::Error(msg) => Err(GatewayError::Forbidden { reason: msg }),\nother => {\n    tracing::error!(response = ?other, \"unexpected AdminResponseBody for InviteIssue\");\n    Err(GatewayError::Internal(anyhow::anyhow!(\"unexpected response shape: {other:?}\")))\n}","handlingStrategy":"try-catch","validationCode":"// Confirm protocol compatibility before sending invite requests\nif client.protocol_version()? != GATEWAY_PROTOCOL_VERSION {\n    return Err(anyhow::anyhow!(\"gateway/daemon version skew\"));\n}","typeGuard":"fn is_invite_response(resp: &AdminResponseBody) -> bool {\n    matches!(resp, AdminResponseBody::Invite(_) | AdminResponseBody::Error(_))\n}","tryCatchPattern":"match issue_invite(State(state), caller, Json(body)).await {\n    Ok(resp) => resp,\n    Err(GatewayError::Internal(e)) if e.to_string().contains(\"unexpected response shape\") => {\n        tracing::error!(%e, \"daemon returned wrong body for InviteIssue\");\n        upgrade_or_reconnect()\n    }\n    Err(e) => handle(e),\n}","preventionTips":["Deploy gateway and daemon together or gate on a protocol version handshake","Add exhaustively-tested mappings for every AdminResponseBody variant per request kind","Include unit tests where the daemon returns each variant for InviteIssue"],"tags":["rust","ipc","response-shape","gateway","invites"],"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"}