astrid-runtime/astrid · error · GatewayError::Internal
unexpected response for GetStatus
Error message
unexpected response for GetStatus: {other:?} What it means
`get_status` in `system.rs` sends `KernelRequest::GetStatus` over the bus with `daemon_kernel_error` error mapping, then matches the `KernelResponse`. If the kernel replies with anything other than `Status` or `Error` — a protocol mismatch — the catch-all arm raises a 500 `GatewayError::Internal` with "unexpected response for GetStatus". It signals the gateway and kernel disagree on the response protocol.
Solutions
- Read the `{other:?}` payload in logs to identify the unexpected `KernelResponse` variant.
- Redeploy gateway and kernel from matching versions.
- Add an explicit match arm for the observed variant in `get_status`.
- Check the kernel logs for the corresponding GetStatus handling to find the source of the mismatched reply.
Example fix
// before
other => Err(GatewayError::Internal(anyhow::anyhow!(
"unexpected response for GetStatus: {other:?}"
))),
// after
KernelResponse::StatusV2(s) => Ok(Json(Status::from(s))),
other => Err(GatewayError::Internal(anyhow::anyhow!(
"unexpected response for GetStatus: {other:?}"
))), Defensive patterns
Strategy: type-guard
Type guard
fn as_status(resp: KernelResponse) -> Option<Status> {
match resp { KernelResponse::Status(s) => Some(s), _ => None }
} Try / catch
match status.get_status().await {
Ok(s) => s,
Err(e) if e.message.starts_with("unexpected response for GetStatus") => {
// protocol mismatch: check versions, retry after redeploy
Err(ProtocolMismatch)
}
Err(GatewayError::Forbidden { reason }) => return Err(reason.into()),
Err(e) => return Err(e.into()),
} Prevention
- Keep gateway and kernel on the same release train; status endpoints are the first to break on drift.
- Cover every `KernelResponse` variant in match arms with compile-time exhaustiveness checks.
- Add a version-compatibility handshake/log line at gateway startup.
- When adding a kernel response variant, update all catch-all sites in routes/system.rs in the same PR.
When it happens
Trigger: GET on the system status route when the kernel returns a `KernelResponse` variant that is neither `Status(_)` nor `Error(_)` (e.g. a variant added in a newer kernel, or a mismatched reply type routed back).
Common situations: Mixed-version deployment where the kernel emits a new response variant the gateway binary doesn't know; a routing bug delivering another request's response to the status caller; hand-edited or stale protocol definitions on one side.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- kernel returned an unexpected storage lifecycle response
- kernel returned an unexpected storage mount response
- unexpected admin response shape
- unexpected response from kernel
- unexpected response shape for GetCapsuleMetadata
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/12a45b7d6d42abf9.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-gateway/src/routes/system.rs:43
(status = 200, description = "`DaemonStatus` JSON shape: `{ pid, started_at, uptime_secs, active_connections, ephemeral, capsules: { loaded, failed }, session_id }`.", content_type = "application/json"),
(status = 401, body = ErrorBody),
(status = 403, body = ErrorBody, description = "Caller lacks `system:status`."),
)
)]
pub async fn get_status(
State(state): State<Arc<GatewayState>>,
req: Request<axum::body::Body>,
) -> GatewayResult<Json<DaemonStatus>> {
let caller = caller_from(&req)?.clone();
let client = state.kernel_client_for(&caller)?;
let resp = client
.request(KernelRequest::GetStatus)
.await
.map_err(daemon_kernel_error)?;
match resp {
KernelResponse::Status(s) => Ok(Json(s)),
KernelResponse::Error(msg) => Err(GatewayError::Forbidden { reason: msg }),
other => Err(GatewayError::Internal(anyhow::anyhow!(
"unexpected response for GetStatus: {other:?}"
))),
}
}
#[utoipa::path(
get,
path = "/api/sys/readiness",
tag = "system",
responses(
(status = 200, description = "`AgentLoopReadiness` JSON shape: `{ ready: bool, prompt_subscribers: [string], response_publishers: [string], unsatisfied_required_imports: [{ capsule, namespace, interface, requirement }], loaded_capsules: [string] }`. `ready` is false when the installed capsule set can't serve an agent chat turn.", content_type = "application/json"),
(status = 401, body = ErrorBody),
(status = 403, body = ErrorBody, description = "Caller lacks `capsule:list`."),
)
)]
pub async fn get_readiness(
State(state): State<Arc<GatewayState>>,
req: Request<axum::body::Body>,View on GitHub (pinned to affd8760f4)