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

gateway is not wired to a live event bus; live feed unavaila

Error message

gateway is not wired to a live event bus; live feed unavailable

What it means

The public SSE `get_stream` route in `stream.rs` clones the event bus from `GatewayState` and returns a 500 `GatewayError::Internal` with "gateway is not wired to a live event bus; live feed unavailable" when it is absent. The live event feed requires a daemon-connected gateway; without a bus there is no stream source. Like the sessions variant, this is a server wiring condition.

Source

Thrown at crates/astrid-gateway/src/routes/stream.rs:123

#[utoipa::path(
    get,
    path = "/api/agent/stream",
    tag = "agent",
    responses(
        (status = 200, description = "Long-lived Server-Sent Events feed of the caller's own conversation activity across all threads. `event: ready` first, then `event: agent` (in-flight turn events) and `event: session_event` (thread lifecycle). Cross-principal events are never delivered.", content_type = "text/event-stream"),
        (status = 401, body = ErrorBody, description = "Missing / invalid bearer."),
        (status = 500, body = ErrorBody, description = "Gateway not wired to a live event bus."),
    )
)]
pub async fn get_stream(
    State(state): State<Arc<GatewayState>>,
    req: Request<axum::body::Body>,
) -> GatewayResult<Sse<impl Stream<Item = Result<Event, Infallible>>>> {
    let caller = caller_from(&req)?;
    metrics::counter!("astrid_gateway_agent_stream_total").increment(1);

    let Some(bus) = state.event_bus.clone() else {
        return Err(GatewayError::Internal(anyhow::anyhow!(
            "gateway is not wired to a live event bus; live feed unavailable"
        )));
    };

    let principal = caller.principal.to_string();

    // Subscribe FIRST, both routes scoped to the caller's principal. The
    // outer `Some` marks the route as scoped; the inner `Some(principal)` is
    // the security boundary — a foreign-principal event is dropped at
    // enqueue and never enters this route's budget (see module docs). A
    // fresh per-call UUID isolates this connection's routes from any other
    // live feed.
    let conn_uuid = Uuid::new_v4();
    let scope = Some(Some(principal.clone()));
    let mut agent_rx = bus.subscribe_topic_routed_scoped(
        conn_uuid,
        TOPIC_AGENT_EVENTS,
        "gateway",

View on GitHub (pinned to affd8760f4)

Solutions

  1. Start the gateway with a live event bus (co-located daemon) so `state.event_bus` is set.
  2. Check gateway startup logs for event-bus initialization errors and fix the wiring config.
  3. Point the SSE client at a daemon-connected gateway instance.
  4. Return a clearer 503/disabled response for streaming routes when no bus is configured.
Defensive patterns

Strategy: validation

Validate before calling

// probe the stream endpoint's precondition: only subscribe on bus-connected gateways
if (!gatewayMetadata.hasEventBus) {
  throw new Error("live feed unavailable: gateway is not daemon-connected");
}

Try / catch

const es = new EventSource(url);
es.onerror = (ev) => {
  // server returned 500 'not wired to a live event bus' -> backoff and re-discover a bus-connected gateway
  scheduleReconnectWithFallback();
};

Prevention

When it happens

Trigger: A client opens the SSE stream endpoint (after passing `caller_from` auth) while `state.event_bus` is `None` because the gateway runs without a co-located daemon or bus init failed.

Common situations: Pointing clients at a standalone/headless gateway instance for live events; event bus initialization silently failing at startup; environments where streaming endpoints are exposed but daemon wiring is disabled.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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