{"record":{"id":"8ef074e5aee4405e","repo":"redis/node-redis","slug":"the-client-is-offline","errorCode":null,"errorMessage":"The client is offline","messagePattern":"The client is offline","errorType":"exception","errorClass":"ClientOfflineError","httpStatus":null,"severity":"warning","filePath":"packages/client/lib/cluster/cluster-slots.ts","lineNumber":995,"sourceCode":"      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\n    return [...this.masters, ...this.replicas];\n  }\n\n  /** Master fan-out target nodes, WITHOUT connecting (see {@link getAllNodes}). */","sourceCodeStart":977,"sourceCodeEnd":1013,"githubUrl":"https://github.com/redis/node-redis/blob/90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58/packages/client/lib/cluster/cluster-slots.ts#L977-L1013","documentation":"Thrown by RedisClusterSlots.#assertReady() when #isOpen is true but #isReady is false. The cluster is open (connect() was called) but has not finished discovering topology and establishing node clients — or it lost readiness during a rediscovery. Commands are rejected because the slot map is not yet populated enough to route correctly.","triggerScenarios":"Issuing a command immediately after cluster.connect() resolves but before the 'ready'/'connect' event fires (rare). Issuing commands during a topology refresh that temporarily unset #isReady. Issuing commands right after connect() failed partway and left the cluster open but not ready.","commonSituations":"Not awaiting the connect()/ready event fully; race between command issuance and initial discovery completion; the cluster connect threw after setting #isOpen but the caller continued issuing commands; reconnect logic issuing commands before rediscovery completes.","solutions":["Await cluster.connect() fully and listen for the 'ready' event before issuing commands.","Guard with if (cluster.isReady) or retry with backoff until isReady is true.","Ensure connect() did not throw; if it did, re-connect before issuing commands.","During topology refreshes, queue or retry commands rather than assuming continuous readiness."],"exampleFix":"// before\ncluster.connect(); // not awaited\ncluster.set('k', 'v'); // throws ClientOfflineError\n\n// after\nawait cluster.connect(); // resolves when ready\nif (!cluster.isReady) await once(cluster, 'ready');\nawait cluster.set('k', 'v');","handlingStrategy":"validation","validationCode":"if (!cluster.isReady) {\n  await once(cluster, 'ready');\n}","typeGuard":"function clusterIsReady(cluster: RedisClusterType): boolean {\n  return cluster.isReady;\n}","tryCatchPattern":"while (!cluster.isReady) {\n  await new Promise(r => setTimeout(r, 50));\n}\nawait cluster.set('k', 'v');","preventionTips":["Await connect() and confirm isReady before issuing commands.","Listen for the 'ready' event during startup.","Retry/backoff if a command hits ClientOfflineError during topology refresh."],"tags":["cluster","lifecycle","readiness","timing"],"backgroundTag":null,"analyzedSha":"90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58","analyzedAt":"2026-08-11T15:37:21.243Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}