astrid-runtime/astrid · error · GatewayError::Internal
registry reply not JSON: {e}
Error message
registry reply not JSON: {e} What it means
`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.
Source
Thrown at crates/astrid-gateway/src/routes/models.rs:152
///
/// Pure core of the SET response mapping, lifted out of the async handler so
/// the shape decision — including the explicit-`null` edge — is unit-testable
/// without a live bus.
enum SetActiveOutcome {
/// Registry persisted an entry; the inner value is the canonical
/// provider-entry to return as a 200 body.
Bound(serde_json::Value),
/// Registry rejected the id; the string is its verbatim message (→ 400).
Rejected(String),
/// Reply carried neither a non-null `active_model` nor an `error` — a
/// shape the registry contract says cannot happen (→ 500).
Malformed,
}
fn registry_reply_payload_json(payload: &IpcPayload) -> GatewayResult<serde_json::Value> {
let bytes = payload
.to_guest_bytes()
.map_err(|e| GatewayError::Internal(anyhow::anyhow!("registry reply not JSON: {e}")))?;
serde_json::from_slice(&bytes)
.map_err(|e| GatewayError::Internal(anyhow::anyhow!("registry reply not JSON: {e}")))
}
/// Classify a `set_active_model` registry reply into the response the handler
/// returns.
///
/// An `error` string maps to [`SetActiveOutcome::Rejected`]. A **non-null**
/// `active_model` maps to [`SetActiveOutcome::Bound`]. An explicit JSON `null`
/// for `active_model` is treated as ABSENT — a successful bind always carries
/// the canonical entry, so a `null` is a malformed reply, not a `200 null`.
/// This mirrors typed deserialization where `active_model: null` → `None`.
/// Anything else is [`SetActiveOutcome::Malformed`].
fn classify_set_active_reply(reply: &serde_json::Value) -> SetActiveOutcome {
if let Some(error) = reply.get("error").and_then(serde_json::Value::as_str) {
return SetActiveOutcome::Rejected(error.to_string());
}
if let Some(active) = reply.get("active_model").filter(|v| !v.is_null()) {View on GitHub (pinned to affd8760f4)
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.
Example fix
// before
let bytes = payload
.to_guest_bytes()
.map_err(|e| GatewayError::Internal(anyhow::anyhow!("registry reply not JSON: {e}")))?;
// after
let bytes = payload.to_guest_bytes().map_err(|e| {
tracing::error!(?e, payload_kind = payload.kind(), "registry reply payload not guest-bytes");
GatewayError::Internal(anyhow::anyhow!("registry reply not JSON: {e}"))
})?; Defensive patterns
Strategy: validation
Validate before calling
fn reply_is_byte_payload(payload: &IpcPayload) -> bool {
matches!(payload, IpcPayload::Bytes(_) | IpcPayload::GuestBytes(_))
}
// call before registry_reply_payload_json
if !reply_is_byte_payload(&payload) { return Err(badReplyKind()); } Type guard
fn is_guest_bytes_payload(p: &IpcPayload) -> bool {
matches!(p, IpcPayload::GuestBytes(_))
} Try / catch
match registry_reply_payload_json(&payload) {
Ok(v) => v,
Err(e) if e.message().contains("registry reply not JSON") => {
tracing::error!(%e, "registry sent non-byte/invalid payload");
Default::default()
}
Err(e) => return Err(e),
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
- registry did not respond
- registry reply was not an IPC message
- MCP gateway rejected {operation:?}: {}
- daemon request: {e}
- unexpected response shape: {other:?}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/3f0e02ca575923eb.
Report an issue: GitHub.