{"record":{"id":"dbd5db6b95ba8057","repo":"hasura/graphql-engine","slug":"poller-with-operation-id-already-exists","errorCode":null,"errorMessage":"poller with operation_id {} already exists","messagePattern":"poller with operation_id (.+?) already exists","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/crates/graphql/graphql-ws/src/protocol/subscribe.rs","lineNumber":21,"sourceCode":"use crate::poller;\nuse crate::websocket::types as ws;\nuse ::pre_response_plugin::execute::PreResponsePluginResponse;\nuse axum::http;\nuse blake2::{Blake2b, Digest};\nuse engine_types::ExposeInternalErrors;\nuse graphql_frontend::{ExecuteQueryResult, RootFieldResult, process_response};\nuse graphql_ir::RequestPlan;\nuse hasura_authn_core::Session;\nuse indexmap::IndexMap;\nuse nonempty::NonEmpty;\nuse pre_parse_plugin::execute as pre_parse_plugin;\nuse pre_response_plugin::execute as pre_response_plugin;\n\n#[derive(thiserror::Error, Debug)]\nenum Error {\n    #[error(\"graphql-ws protocol is not initialized\")]\n    NotInitialized,\n    #[error(\"poller with operation_id {} already exists\", operation_id.0)]\n    PollerAlreadyExists { operation_id: OperationId },\n    #[error(\"error in pre-parse plugin: {0}\")]\n    PreParsePlugin(#[from] pre_parse_plugin::Error),\n}\n\nimpl tracing_util::TraceableError for Error {\n    fn visibility(&self) -> tracing_util::ErrorVisibility {\n        tracing_util::ErrorVisibility::User\n    }\n}\n\n/// Handles the subscription message from the client.\n/// It either starts a new poller or sends a close message if the poller with given operation_id already exists.\npub async fn handle_subscribe<M: WebSocketMetrics>(\n    client_address: std::net::SocketAddr,\n    connection: ws::Connection<M>,\n    operation_id: OperationId,\n    payload: lang_graphql::http::RawRequest,","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/hasura/graphql-engine/blob/724551b9ae87845594ef0408cff0e50eb6c90dc5/v3/crates/graphql/graphql-ws/src/protocol/subscribe.rs#L3-L39","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Generate a unique operation id (e.g. UUID) for every subscribe message","Send `stop`/`complete` for the previous operation before re-subscribing with the same id","Make subscribe handlers idempotent by treating a duplicate as a stop+resubscribe","Check for duplicate message delivery (network retries, buggy reconnect logic)"],"exampleFix":"// before\nconst opId = 'my-query';\n// after\nconst opId = crypto.randomUUID();","handlingStrategy":"validation","validationCode":"const active = new Set();\nfunction nextOpId(){ let id; do { id = crypto.randomUUID(); } while(active.has(id)); active.add(id); return id; }","typeGuard":null,"tryCatchPattern":"catch (e) { if (/poller with operation_id .* already exists/.test(String(e))) { stop(oldId); resubscribeWithNewId(); } }","preventionTips":["Use unique ids per subscription","Send stop before re-subscribing","Make subscribe idempotent server-side"],"tags":["graphql-ws","subscriptions","duplicate-operation","protocol-state"],"backgroundTag":"duplicate-subscription-id","analyzedSha":"724551b9ae87845594ef0408cff0e50eb6c90dc5","analyzedAt":"2026-08-28T07:32:55.105Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}