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

  1. Inspect the inner `{e}` to see which payload conversion failed and what payload variant arrived.
  2. Verify the registry capsule version matches the gateway's expected IPC payload format.
  3. Ensure the registry handler replies with a byte-compatible (guest-bytes) payload, not an opaque variant.
  4. 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

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


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