hasura/graphql-engine · error · Error

poller with operation_id {} already exists

Error message

poller with operation_id {} already exists

What it means

The subscribe handler detected an internal conflict: a poller for the given `OperationId` already exists when a new one was being registered. Each subscription operation should map to exactly one poller; a duplicate registration attempt triggers this error.

Source

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

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>,
    operation_id: OperationId,
    payload: lang_graphql::http::RawRequest,

View on GitHub (pinned to 724551b9ae)

Solutions

  1. Generate a unique operation id (e.g. UUID) for every subscribe message
  2. Send `stop`/`complete` for the previous operation before re-subscribing with the same id
  3. Make subscribe handlers idempotent by treating a duplicate as a stop+resubscribe
  4. Check for duplicate message delivery (network retries, buggy reconnect logic)

Example fix

// before
const opId = 'my-query';
// after
const opId = crypto.randomUUID();
Defensive patterns

Strategy: validation

Validate before calling

const active = new Set();
function nextOpId(){ let id; do { id = crypto.randomUUID(); } while(active.has(id)); active.add(id); return id; }

Try / catch

catch (e) { if (/poller with operation_id .* already exists/.test(String(e))) { stop(oldId); resubscribeWithNewId(); } }

Prevention

When it happens

Trigger: A client reusing the same operation id for a new `subscribe` message without first completing/stopping the previous subscription; internal code paths registering a poller twice for the same OperationId (e.g. after a retry or duplicate message delivery).

Common situations: Client generating colliding operation ids (e.g. a constant id instead of unique ids); at-least-once message delivery causing duplicate subscribes; retry logic resending subscribe without a unique id.

Related errors


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