{"record":{"id":"3f0e02ca575923eb","repo":"astrid-runtime/astrid","slug":"registry-reply-not-json-e","errorCode":null,"errorMessage":"registry reply not JSON: {e}","messagePattern":"registry reply not JSON: (.+?)","errorType":"http","errorClass":"GatewayError::Internal","httpStatus":500,"severity":"error","filePath":"crates/astrid-gateway/src/routes/models.rs","lineNumber":152,"sourceCode":"///\n/// Pure core of the SET response mapping, lifted out of the async handler so\n/// the shape decision — including the explicit-`null` edge — is unit-testable\n/// without a live bus.\nenum SetActiveOutcome {\n    /// Registry persisted an entry; the inner value is the canonical\n    /// provider-entry to return as a 200 body.\n    Bound(serde_json::Value),\n    /// Registry rejected the id; the string is its verbatim message (→ 400).\n    Rejected(String),\n    /// Reply carried neither a non-null `active_model` nor an `error` — a\n    /// shape the registry contract says cannot happen (→ 500).\n    Malformed,\n}\n\nfn registry_reply_payload_json(payload: &IpcPayload) -> GatewayResult<serde_json::Value> {\n    let bytes = payload\n        .to_guest_bytes()\n        .map_err(|e| GatewayError::Internal(anyhow::anyhow!(\"registry reply not JSON: {e}\")))?;\n    serde_json::from_slice(&bytes)\n        .map_err(|e| GatewayError::Internal(anyhow::anyhow!(\"registry reply not JSON: {e}\")))\n}\n\n/// Classify a `set_active_model` registry reply into the response the handler\n/// returns.\n///\n/// An `error` string maps to [`SetActiveOutcome::Rejected`]. A **non-null**\n/// `active_model` maps to [`SetActiveOutcome::Bound`]. An explicit JSON `null`\n/// for `active_model` is treated as ABSENT — a successful bind always carries\n/// the canonical entry, so a `null` is a malformed reply, not a `200 null`.\n/// This mirrors typed deserialization where `active_model: null` → `None`.\n/// Anything else is [`SetActiveOutcome::Malformed`].\nfn classify_set_active_reply(reply: &serde_json::Value) -> SetActiveOutcome {\n    if let Some(error) = reply.get(\"error\").and_then(serde_json::Value::as_str) {\n        return SetActiveOutcome::Rejected(error.to_string());\n    }\n    if let Some(active) = reply.get(\"active_model\").filter(|v| !v.is_null()) {","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-gateway/src/routes/models.rs#L134-L170","documentation":"`registry_reply_payload_json` converts an IPC payload from the model-registry reply into bytes via `to_guest_bytes()`. If that byte extraction fails, the error is wrapped as `GatewayError::Internal` with `registry reply not JSON: {e}`. It means the registry's reply payload could not even be extracted as bytes, before JSON parsing is attempted.","triggerScenarios":"A reply `IpcPayload` whose `to_guest_bytes()` conversion fails — e.g. a payload variant that does not support guest-byte extraction, or a capsule-side encoding fault in the registry response.","commonSituations":"A registry capsule built with an incompatible payload encoding; a misbehaving test/fixture capsule replying with a non-byte payload type; capsule runtime memory/serialization failure when materializing the reply.","solutions":["Inspect the inner `{e}` to see which payload conversion failed and what payload variant arrived.","Verify the registry capsule version matches the gateway's expected IPC payload format.","Ensure the registry handler replies with a byte-compatible (guest-bytes) payload, not an opaque variant.","Reload/rebuild the registry capsule if its runtime state is corrupted."],"exampleFix":"// before\nlet bytes = payload\n    .to_guest_bytes()\n    .map_err(|e| GatewayError::Internal(anyhow::anyhow!(\"registry reply not JSON: {e}\")))?;\n// after\nlet bytes = payload.to_guest_bytes().map_err(|e| {\n    tracing::error!(?e, payload_kind = payload.kind(), \"registry reply payload not guest-bytes\");\n    GatewayError::Internal(anyhow::anyhow!(\"registry reply not JSON: {e}\"))\n})?;","handlingStrategy":"validation","validationCode":"fn reply_is_byte_payload(payload: &IpcPayload) -> bool {\n    matches!(payload, IpcPayload::Bytes(_) | IpcPayload::GuestBytes(_))\n}\n// call before registry_reply_payload_json\nif !reply_is_byte_payload(&payload) { return Err(badReplyKind()); }","typeGuard":"fn is_guest_bytes_payload(p: &IpcPayload) -> bool {\n    matches!(p, IpcPayload::GuestBytes(_))\n}","tryCatchPattern":"match registry_reply_payload_json(&payload) {\n    Ok(v) => v,\n    Err(e) if e.message().contains(\"registry reply not JSON\") => {\n        tracing::error!(%e, \"registry sent non-byte/invalid payload\");\n        Default::default()\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Ensure registry capsules always reply with guest-bytes-compatible payloads.","Add a capsule conformance test asserting reply payload kind.","Keep capsule and gateway IPC payload definitions in sync.","Log payload kind/size on conversion failure for diagnosis."],"tags":["ipc","serialization","registry","gateway"],"backgroundTag":"json-unmarshal-failed","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"}