redis/node-redis · error · ClientClosedError

The client is closed

Error message

The client is closed

What it means

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.

Solutions

  1. Call and await cluster.connect() before issuing any command.
  2. Guard command-issuing code with if (cluster.isOpen).
  3. Do not issue commands after disconnect()/destroy(); re-connect first if needed.
  4. Centralize cluster lifecycle so command code only runs after connect() resolves.

Example fix

// before
const cluster = createCluster({ rootNodes });
await cluster.set('k', 'v'); // throws ClientClosedError (never connected)

// after
const cluster = createCluster({ rootNodes });
await cluster.connect();
await cluster.set('k', 'v');
Defensive patterns

Strategy: validation

Validate before calling

if (!cluster.isOpen) {
  throw new Error('Cluster is not open. Call await cluster.connect() first.');
}

Type guard

function clusterIsOpen(cluster: RedisClusterType): boolean {
  return cluster.isOpen;
}

Try / catch

try {
  await cluster.set('k', 'v');
} catch (e) {
  if (e instanceof ClientClosedError) {
    await cluster.connect();
    await cluster.set('k', 'v');
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of redis/node-redis@90fd0652bc (2026-08-11). Data as JSON: /api/errors/01b6bbdac056a27a. Report an issue: GitHub.

Appendix: source

Thrown at packages/client/lib/cluster/cluster-slots.ts:991

      promises.push(fn(client));
    }

    if (this.pubSubNode) {
      promises.push(fn(this.pubSubNode.client));
      this.pubSubNode = undefined;
    }

    this.#resetSlots();
    this.nodeByAddress.clear();
    this.#reconnectionTracker.clear();

    await Promise.allSettled(promises);
    this.#emit('disconnect');
  }

  #assertReady() {
    if (!this.#isOpen) {
      throw new ClientClosedError();
    }

    if (!this.#isReady) {
      throw new ClientOfflineError();
    }
  }

  /**
   * All fan-out target nodes (masters + replicas), WITHOUT connecting. The
   * caller connects each node lazily in its own per-node promise so a single
   * failed connect rejects only that node's execution — letting reducers such
   * as `one_succeeded` still see the reachable shards — instead of a `Promise.all`
   * over the connects failing the whole route up front. Excludes the dedicated
   * PubSub connection (not in `masters`/`replicas`).
   */
  getAllNodes() {
    this.#assertReady();

View on GitHub (pinned to 90fd0652bc)