hasura/graphql-engine · error · Error

graphql-ws protocol is not initialized

Error message

graphql-ws protocol is not initialized

What it means

Thrown by the graphql-ws subscribe handler when a `subscribe` message arrives before the protocol has been initialized via `connection_init`. The protocol state machine requires a successful initialization before any subscriptions can be created.

Source

Thrown at v3/crates/graphql/graphql-ws/src/protocol/subscribe.rs:19

use super::types::{ConnectionInitState, OperationId, ServerMessage};
use crate::metrics::WebSocketMetrics;
use crate::poller;
use crate::websocket::types as ws;
use ::pre_response_plugin::execute::PreResponsePluginResponse;
use axum::http;
use blake2::{Blake2b, Digest};
use engine_types::ExposeInternalErrors;
use graphql_frontend::{ExecuteQueryResult, RootFieldResult, process_response};
use graphql_ir::RequestPlan;
use hasura_authn_core::Session;
use indexmap::IndexMap;
use nonempty::NonEmpty;
use pre_parse_plugin::execute as pre_parse_plugin;
use pre_response_plugin::execute as pre_response_plugin;

#[derive(thiserror::Error, Debug)]
enum Error {
    #[error("graphql-ws protocol is not initialized")]
    NotInitialized,
    #[error("poller with operation_id {} already exists", operation_id.0)]
    PollerAlreadyExists { operation_id: OperationId },
    #[error("error in pre-parse plugin: {0}")]
    PreParsePlugin(#[from] pre_parse_plugin::Error),
}

impl tracing_util::TraceableError for Error {
    fn visibility(&self) -> tracing_util::ErrorVisibility {
        tracing_util::ErrorVisibility::User
    }
}

/// Handles the subscription message from the client.
/// It either starts a new poller or sends a close message if the poller with given operation_id already exists.
pub async fn handle_subscribe<M: WebSocketMetrics>(
    client_address: std::net::SocketAddr,
    connection: ws::Connection<M>,

View on GitHub (pinned to 724551b9ae)

Solutions

  1. Ensure the client sends `connection_init` first and waits for `connection_ack` before subscribing
  2. If reconnecting, re-run the full handshake instead of reusing the old socket state
  3. Check that connection_init did not fail (auth errors often cause silent non-initialization)
  4. Add client-side state tracking so subscribe is only called after ack

Example fix

// before
ws.onopen = () => ws.send(JSON.stringify({type:'subscribe',...}));
// after
let ready = false;
ws.onopen = () => ws.send(JSON.stringify({type:'connection_init',payload:{...}}));
ws.onmessage = (m) => { if (JSON.parse(m.data).type==='connection_ack') ready = true; };
ws.sendSubscribe = () => { if (ready) ws.send(JSON.stringify({type:'subscribe',...})); };
Defensive patterns

Strategy: validation

Validate before calling

let initialized = false;
function assertInitialized(){ if(!initialized) throw new Error('connection not initialized'); }

Try / catch

catch (e) { if (String(e).includes('not initialized')) { sendInit(); /* wait ack then resubscribe */ } }

Prevention

When it happens

Trigger: Sending a `subscribe` message on a WebSocket before sending (or before the server has acknowledged) a `connection_init` message; also when initialization failed silently and the client proceeds to subscribe.

Common situations: Client library that skips connection_init; race where the client sends subscribe before processing the connection_ack; reconnect logic that reuses stale state and skips re-initialization.

Related errors


AI-assisted analysis of hasura/graphql-engine@724551b9ae (2026-08-28). Data as JSON: /api/errors/f265c80556e17aa2. Report an issue: GitHub.