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

registry reply was not an IPC message

Error message

registry reply was not an IPC message

What it means

After receiving an event on the reply channel, `registry_round_trip` destructures it expecting `AstridEvent::Ipc`. Any other event variant triggers `GatewayError::Internal('registry reply was not an IPC message')`. This guards the invariant that only IPC messages should arrive on the registry reply subscription.

Source

Thrown at crates/astrid-gateway/src/routes/models.rs:279

        .unwrap_or_else(tokio::time::Instant::now);
    loop {
        let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
        // No budget left (deadline already elapsed, or zero remaining): break
        // to the timeout path rather than calling `recv(Some(ZERO))`, whose
        // behaviour with a zero duration is implementation-defined. The
        // absolute deadline above keeps total wait bounded regardless.
        if remaining.is_zero() {
            return Err(GatewayError::Internal(anyhow::anyhow!(
                "registry did not respond"
            )));
        }
        let Some(event) = reply_rx.recv(Some(remaining)).await else {
            return Err(GatewayError::Internal(anyhow::anyhow!(
                "registry did not respond"
            )));
        };
        let AstridEvent::Ipc { message, .. } = &*event else {
            return Err(GatewayError::Internal(anyhow::anyhow!(
                "registry reply was not an IPC message"
            )));
        };
        if !expected_source_ids.contains(&message.source_id) {
            continue;
        }
        // Extract the guest-facing payload. Capsule `publish_json` arrives as
        // `Custom { data }` when the JSON has no known IPC `type`; using the
        // guest bytes unwraps that data instead of exposing the internal tagged
        // wrapper to the HTTP API.
        let value = registry_reply_payload_json(&message.payload)?;
        // Skip a reply that belongs to a different concurrent same-principal
        // SET (foreign `corr_id`); keep waiting within the remaining budget.
        if reply_satisfies_corr_id(&value, corr_id) {
            return Ok(value);
        }
    }
}

View on GitHub (pinned to affd8760f4)

Solutions

  1. Log the received event variant to identify what is being published to the reply topic.
  2. Narrow the reply subscription to the exact response topic/correlation key so unrelated events cannot arrive.
  3. Align event enum versions between gateway and bus publishers.
  4. If other variants are legitimately possible, handle/skip them instead of failing the round trip.

Example fix

// before
let AstridEvent::Ipc { message, .. } = &*event else {
    return Err(GatewayError::Internal(anyhow::anyhow!(
        "registry reply was not an IPC message"
    )));
};
// after
let AstridEvent::Ipc { message, .. } = &*event else {
    tracing::debug!(event_kind = event.kind(), "ignoring non-IPC event on reply topic");
    continue;
};
Defensive patterns

Strategy: type-guard

Type guard

fn as_ipc_event(event: &AstridEvent) -> Option<&IpcMessage> {
    match event {
        AstridEvent::Ipc { message, .. } => Some(message),
        _ => None,
    }
}

Try / catch

match registry_round_trip(state, principal, req_topic, resp_topic, payload, corr).await {
    Err(e) if e.message().contains("was not an IPC message") => {
        tracing::warn!("non-IPC event on reply topic; treating as transient");
        registry_round_trip(state, principal, req_topic, resp_topic, payload, corr).await
    }
    r => r,
}

Prevention

When it happens

Trigger: The event bus delivers a non-IPC `AstridEvent` (e.g. a lifecycle, log, or control event) to the registry reply subscription — possible from over-broad topic subscriptions or a misbehaving bus publisher.

Common situations: Wildcard/overlapping topic subscriptions routing unrelated events to the reply channel; a bus implementation broadcasting control events to all subscribers; a test harness publishing raw events; version mismatch where event enums diverged between publisher and gateway.

Related errors


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