astrid-runtime/astrid · error · GatewayError::Internal
registry set_active_model reply carried neither an 'error' n
Error message
registry set_active_model reply carried neither an 'error' nor a non-null 'active_model' (an explicit-null 'active_model' is treated as absent)
What it means
`set_active_model_inner` classifies the registry's reply to PUT /api/models/active via `classify_set_active_reply`. A valid reply must be either `{ error }` (rejection -> 400) or `{ active_model: <non-null entry> }` (success -> 200). `SetActiveOutcome::Malformed` means the reply had neither an 'error' nor a non-null 'active_model' — an explicit null active_model is deliberately treated as malformed, not as success — so the gateway raises a 500 Internal.
Source
Thrown at crates/astrid-gateway/src/routes/models.rs:533
SET_ACTIVE_RESPONSE,
// The registry reads `model_id` (also accepted under `data`); the
// gateway forwards the raw `id` untouched and never interprets it.
// `corr_id` is echoed verbatim by the registry for reply correlation.
json!({ "model_id": body.id, "corr_id": corr_id }),
Some(corr_id.as_str()),
)
.await?;
// Success: `{ "status": "ok", "active_model": <entry> }` → return the
// persisted entry so the caller sees the canonical id the registry
// bound. Error: `{ "error": "<msg>" }` → 400 with the registry's
// message surfaced verbatim (the gateway does not reinterpret
// resolution / ambiguity errors). An explicit `null` active_model is a
// malformed reply, not a success — see `classify_set_active_reply`.
match classify_set_active_reply(&reply) {
SetActiveOutcome::Bound(active) => Ok(Json(active)),
SetActiveOutcome::Rejected(message) => Err(GatewayError::BadRequest(message)),
SetActiveOutcome::Malformed => Err(GatewayError::Internal(anyhow::anyhow!(
"registry set_active_model reply carried neither an 'error' nor a \
non-null 'active_model' (an explicit-null 'active_model' is treated \
as absent)"
))),
}
}
#[cfg(test)]
mod tests {
use super::{
GET_ACTIVE_REQUEST, GET_ACTIVE_RESPONSE, SetActiveOutcome, classify_set_active_reply,
registry_reply_payload_json, registry_round_trip, reply_satisfies_corr_id,
};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use crate::error::GatewayError;
use crate::routes::WorkspaceContext;View on GitHub (pinned to affd8760f4)
Solutions
- Check gateway and registry capsule version compatibility and upgrade/downgrade so both use the same set_active reply schema.
- Log the raw reply payload at the registry and gateway boundary and fix the registry so a successful bind always returns a non-null 'active_model' entry and a failure returns 'error'.
- Inspect `classify_set_active_reply` to confirm which reply shape was received, then patch the registry producer accordingly.
- Add a regression test in the registry covering the explicit-null active_model case (mirroring the gateway's classify tests).
Example fix
// before (registry reply for success)
json!({ "status": "ok", "active_model": null })
// after
json!({ "status": "ok", "active_model": bound_entry }) Defensive patterns
Strategy: validation
Validate before calling
// validate the reply shape before treating it as a bind result
let ok = reply.get("active_model").map_or(false, |v| !v.is_null())
|| reply.get("error").map_or(false, |v| v.is_string());
if !ok {
return Err(anyhow!("malformed set_active_model reply: {reply}"));
} Type guard
fn is_well_formed_set_active_reply(reply: &serde_json::Value) -> bool {
reply.get("error").map_or(false, |v| v.is_string())
|| reply.get("active_model").map_or(false, |v| !v.is_null())
} Try / catch
match classify_set_active_reply(&reply) {
SetActiveOutcome::Bound(active) => Ok(Json(active)),
SetActiveOutcome::Rejected(msg) => Err(GatewayError::BadRequest(msg)),
SetActiveOutcome::Malformed => Err(GatewayError::Internal(anyhow!(
"malformed set_active_model reply: {reply}"
))),
} Prevention
- Pin gateway and registry capsule to protocol-compatible versions; add a contract test for the set_active reply schema
- Have the registry log every reply it emits so malformed payloads are debuggable
- Never emit explicit-null active_model on success; always include the bound entry or an error string
When it happens
Trigger: PUT /api/models/active where `registry_round_trip` returns a reply JSON lacking both keys, or with `active_model: null`, indicating a registry implementation bug or protocol/version mismatch between gateway and registry.
Common situations: Running a newer gateway against an older registry capsule whose set_active reply schema differs (e.g. emits `{status:"ok"}` without active_model); a registry bug that swallows the bind result and serializes null; corrupted/reshaped reply after an intermediary transformation.
Related errors
- unexpected daemon metadata response: {other:?}
- unexpected response from kernel: {body:?}
- unexpected response from kernel: {other:?}
- daemon returned an unexpected status response
- gateway is not wired to a live event bus; agent approval una
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/5119e1793bd51a6c.
Report an issue: GitHub.