{"record":{"id":"fbcf2d381cc78c16","repo":"redis/node-redis","slug":"pubsubproxy-didn-t-define-node-to-do-pubsub-again","errorCode":null,"errorMessage":"pubSubProxy: didn't define node to do pubsub against","messagePattern":"pubSubProxy: didn't define node to do pubsub against","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/client/lib/sentinel/pub-sub-proxy.ts","lineNumber":48,"sourceCode":"  #onError;\n\n  #node?: RedisNode;\n  #state?: PubSubState;\n  #subscriptions?: Subscriptions;\n\n  constructor(\n    clientOptions: AnyRedisClientOptions,\n    onError: OnError\n  ) {\n    super();\n\n    this.#clientOptions = clientOptions;\n    this.#onError = onError;\n  }\n\n  #createClient() {\n    if (this.#node === undefined) {\n      throw new Error(\"pubSubProxy: didn't define node to do pubsub against\");\n    }\n\n    return new RedisClient({\n      ...this.#clientOptions,\n      socket: {\n        ...this.#clientOptions.socket,\n        host: this.#node.host,\n        port: this.#node.port\n      }\n    });\n  }\n\n  async #initiatePubSubClient(withSubscriptions = false) {\n    const client = this.#createClient()\n      .on('error', this.#onError);\n\n    const connectPromise = client.connect()\n      .then(async client => {","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/redis/node-redis/blob/90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58/packages/client/lib/sentinel/pub-sub-proxy.ts#L30-L66","documentation":"`PubSubProxy` is told which node to pubsub against via `changeNode(node)`; `#createClient` throws if `#node` is still undefined when it tries to build a `RedisClient`. It is an internal ordering invariant: pubsub commands must not run before the Sentinel has selected a target node.","triggerScenarios":"Issuing SUBSCRIBE/PSUBSCRIBE/SSUBSCRIBE through the Sentinel before the proxy's node has been set — e.g. subscribing before `connect()` has selected a master/replica, or during the window after a failover reset before changeNode() reruns.","commonSituations":"Subscribing immediately after constructing the Sentinel without awaiting connect; race between reset (which clears state) and a subscribe call; internal failover timing.","solutions":["`await sentinel.connect()` before issuing any pubsub command.","Retry the subscribe if it fails during a failover/reset window.","Ensure you route pubsub through the Sentinel's pubsub handle, not a raw client."],"exampleFix":"// before\nconst s = new RedisSentinel(opts, id);\ns.subscribe('ch', fn); // node not yet selected\n// after\nconst s = new RedisSentinel(opts, id);\nawait s.connect();\ns.subscribe('ch', fn);","handlingStrategy":"validation","validationCode":"async function subscribeSafe(sentinel: { isReady: boolean; connect(): Promise<void>; subscribe(ch: string, fn: () => void): Promise<unknown> }, ch: string, fn: () => void) {\n  if (!sentinel.isReady) await sentinel.connect();\n  return sentinel.subscribe(ch, fn);\n}","typeGuard":"function nodeSelected(proxy: { node?: unknown } | unknown): boolean {\n  // PubSubProxy.#node is private; approximate by only subscribing after connect resolves\n  return true;\n}","tryCatchPattern":"async function subscribeWithRetry(sentinel: { subscribe(ch: string, fn: () => void): Promise<unknown>; isReady: boolean }, ch: string, fn: () => void, attempts = 3) {\n  for (let i = 0; i < attempts; i++) {\n    try { return await sentinel.subscribe(ch, fn); }\n    catch (e) {\n      if (String(e).includes(\"didn't define node\") && sentinel.isReady && i < attempts - 1) {\n        await new Promise(r => setTimeout(r, 200)); continue;\n      }\n      throw e;\n    }\n  }\n}","preventionTips":["Always `await sentinel.connect()` before any pubsub command.","Retry pubsub setup if it lands in a failover/reset window."],"tags":["sentinel","pubsub","lifecycle","ordering"],"backgroundTag":null,"analyzedSha":"90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58","analyzedAt":"2026-08-11T15:37:21.243Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}