astrid-runtime/astrid · error · GatewayError::Internal

unexpected response shape: {other:?}

Error message

unexpected response shape: {other:?}

What it means

post_redeem treats an AdminResponseBody that is neither InviteRedeemed nor Error as a protocol violation and raises Internal with "unexpected response shape: {other:?}". It indicates the daemon replied successfully but with a variant the gateway didn't expect for an InviteRedeem request — usually a version skew between gateway and kernel.

Source

Thrown at crates/astrid-gateway/src/routes/auth.rs:142

    // `InviteRedeem` doesn't need a verified caller principal — the
    // token is the auth and the kernel's admin dispatcher bypasses
    // the cap-gate for this variant. Stamp the IPC message with the
    // `default` principal so the kernel's `resolve_caller` has *a*
    // value to log; the handler ignores it.
    let client = state.admin_client(PrincipalId::default())?;
    let resp = client
        .request(AdminRequestKind::InviteRedeem {
            token: body.token,
            public_key: body.public_key,
            display_name: body.display_name,
        })
        .await
        .map_err(|e| GatewayError::Internal(anyhow::anyhow!("daemon request: {e}")))?;
    let redeemed = match resp {
        AdminResponseBody::InviteRedeemed(r) => r,
        AdminResponseBody::Error(msg) => return Err(GatewayError::Kernel(msg)),
        other => {
            return Err(GatewayError::Internal(anyhow::anyhow!(
                "unexpected response shape: {other:?}"
            )));
        },
    };

    let session_token = mint_bearer(
        &state.signing.signer,
        &redeemed.principal,
        state.config.session_lifetime_secs,
    );
    let session_expires_at_epoch = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map_or(0, |d| d.as_secs())
        .saturating_add(state.config.session_lifetime_secs);

    Ok(Json(RedeemResponse {
        principal: redeemed.principal,
        group: redeemed.group,

View on GitHub (pinned to affd8760f4)

Solutions

  1. Ensure gateway and kernel/daemon are built from compatible versions and redeploy together.
  2. Inspect the {other:?} payload to identify which variant came back and why.
  3. Add/extend a matching AdminResponseBody variant if the protocol intentionally gained a new response.
  4. Report/fix the daemon handler that answered InviteRedeem with the wrong response type.

Example fix

// before: version-skewed pair
gateway v1.2 (expects InviteRedeemed) <-> kernel v1.3 (returns InviteRedeemedV2)
// after: matching versions
$ cargo build --workspace && redeploy gateway + kernel together
Defensive patterns

Strategy: try-catch

Validate before calling

// assert protocol compatibility at deploy time
gatewayVersion === kernelVersion || throw new Error('gateway/kernel version skew');

Type guard

fn is_redeem_response(resp: &AdminResponseBody) -> bool {
    matches!(resp, AdminResponseBody::InviteRedeemed(_) | AdminResponseBody::Error(_))
}

Try / catch

match resp {
    AdminResponseBody::InviteRedeemed(r) => Ok(r),
    AdminResponseBody::Error(msg) => Err(GatewayError::Kernel(msg)),
    other => Err(GatewayError::Internal(anyhow!("unexpected response shape: {other:?}"))),
} // on this error: pin gateway+kernel to matching versions and redeploy

Prevention

When it happens

Trigger: POST /api/auth/redeem where the daemon's response deserializes to an AdminResponseBody variant other than InviteRedeemed or Error (e.g. PairToken, or a newer/older variant).

Common situations: Gateway and kernel built from mismatched versions so request/response enums drifted; a proxy or buggy daemon returning a wrong response type for the request.

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


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