{"record":{"id":"01b6bbdac056a27a","repo":"redis/node-redis","slug":"the-client-is-closed-01b6bb","errorCode":null,"errorMessage":"The client is closed","messagePattern":"The client is closed","errorType":"exception","errorClass":"ClientClosedError","httpStatus":null,"severity":"error","filePath":"packages/client/lib/cluster/cluster-slots.ts","lineNumber":991,"sourceCode":"      promises.push(fn(client));\n    }\n\n    if (this.pubSubNode) {\n      promises.push(fn(this.pubSubNode.client));\n      this.pubSubNode = undefined;\n    }\n\n    this.#resetSlots();\n    this.nodeByAddress.clear();\n    this.#reconnectionTracker.clear();\n\n    await Promise.allSettled(promises);\n    this.#emit('disconnect');\n  }\n\n  #assertReady() {\n    if (!this.#isOpen) {\n      throw new ClientClosedError();\n    }\n\n    if (!this.#isReady) {\n      throw new ClientOfflineError();\n    }\n  }\n\n  /**\n   * All fan-out target nodes (masters + replicas), WITHOUT connecting. The\n   * caller connects each node lazily in its own per-node promise so a single\n   * failed connect rejects only that node's execution — letting reducers such\n   * as `one_succeeded` still see the reachable shards — instead of a `Promise.all`\n   * over the connects failing the whole route up front. Excludes the dedicated\n   * PubSub connection (not in `masters`/`replicas`).\n   */\n  getAllNodes() {\n    this.#assertReady();\n","sourceCodeStart":973,"sourceCodeEnd":1009,"githubUrl":"https://github.com/redis/node-redis/blob/90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58/packages/client/lib/cluster/cluster-slots.ts#L973-L1009","documentation":"Thrown by RedisClusterSlots.#assertReady() (invoked by getClientAndSlotNumber, getAllNodes, getAllMasterNodes, and every command route) when #isOpen is false. The cluster was never connected, or was disconnected/destroyed, so there is no slot map and no node clients to route a command to.","triggerScenarios":"Issuing any command (get, set, etc.) on a cluster before calling cluster.connect(). Issuing commands after cluster.disconnect() or cluster.destroy(). Issuing commands in a reconnect path before the cluster has re-opened.","commonSituations":"Forgetting to await cluster.connect() before first command; calling commands in a constructor that runs before bootstrap; issuing commands after a shutdown flag closed the cluster; test code running before the cluster is connected.","solutions":["Call and await cluster.connect() before issuing any command.","Guard command-issuing code with if (cluster.isOpen).","Do not issue commands after disconnect()/destroy(); re-connect first if needed.","Centralize cluster lifecycle so command code only runs after connect() resolves."],"exampleFix":"// before\nconst cluster = createCluster({ rootNodes });\nawait cluster.set('k', 'v'); // throws ClientClosedError (never connected)\n\n// after\nconst cluster = createCluster({ rootNodes });\nawait cluster.connect();\nawait cluster.set('k', 'v');","handlingStrategy":"validation","validationCode":"if (!cluster.isOpen) {\n  throw new Error('Cluster is not open. Call await cluster.connect() first.');\n}","typeGuard":"function clusterIsOpen(cluster: RedisClusterType): boolean {\n  return cluster.isOpen;\n}","tryCatchPattern":"try {\n  await cluster.set('k', 'v');\n} catch (e) {\n  if (e instanceof ClientClosedError) {\n    await cluster.connect();\n    await cluster.set('k', 'v');\n  } else throw e;\n}","preventionTips":["Always await cluster.connect() before issuing commands.","Guard command code with if (cluster.isOpen).","Do not issue commands after disconnect()/destroy()."],"tags":["cluster","lifecycle","connection"],"backgroundTag":null,"analyzedSha":"90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58","analyzedAt":"2026-08-11T15:37:21.243Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}