redis/node-redis · error · Error

Cluster closed

Error message

Cluster closed

What it means

Thrown from RedisClusterSlots.#discoverWithRootNodes (cluster-slots.ts:256) inside the first half of the root-node scan loop, when #isOpen became false during an in-flight connect()/discovery. This means close()/destroy() ran concurrently with connect() — the discovery honors the teardown and aborts instead of resurrecting a closed cluster.

Source

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

      await this.#discoverWithRootNodes();
      // `destroy()` may have run while discovery was in flight; if so, this
      // resolution is stale and must not resurrect readiness for a session
      // that's already been torn down.
      if (this.#isOpen) {
        this.#isReady = true;
        this.#emit('connect');
      }
    } catch (err) {
      this.#isOpen = false;
      this.#isReady = false;
      throw err;
    }
  }

  async #discoverWithRootNodes() {
    const start = Math.floor(Math.random() * this.#options.rootNodes.length);
    for (let i = start; i < this.#options.rootNodes.length; i++) {
      if (!this.#isOpen) throw new Error('Cluster closed');
      if (await this.#discover(this.#options.rootNodes[i])) {
        return;
      }
    }

    for (let i = 0; i < start; i++) {
      if (!this.#isOpen) throw new Error('Cluster closed');
      if (await this.#discover(this.#options.rootNodes[i])) {
        return;
      }
    }

    throw new RootNodesUnavailableError();
  }

  #resetSlots() {
    this.slots = new Array(RedisClusterSlots.#SLOTS);
    this.masters = [];

View on GitHub (pinned to bb5beb5657)

Solutions

  1. Serialize connect()/close(): do not destroy a cluster whose connect() has not resolved.
  2. Await connect() (or its rejection) before calling close()/destroy().
  3. In tests, await connect().catch(() => {}) in afterEach before destroying.

Example fix

// before
const p = cluster.connect();
cluster.destroy(); // races with discovery -> 'Cluster closed'
await p;

// after
try { await cluster.connect(); } catch {}
await cluster.destroy();
Defensive patterns

Strategy: try-catch

Type guard

function isClusterClosed(e: unknown): boolean {
  return e instanceof Error && /Cluster closed/i.test(e.message);
}

Try / catch

try { await cluster.connect(); }
catch (e) { if (isClusterClosed(e)) { /* expected: concurrent teardown */ return; } throw e; }

Prevention

When it happens

Trigger: cluster.connect() is in progress (iterating root nodes) and another code path calls cluster.close() or destroy() mid-discovery.

Common situations: Shutdown racing with startup; a health check starting a connect that is immediately cancelled; tests that tear down the cluster before discovery finishes.

Related errors


AI-assisted analysis of redis/node-redis@bb5beb5657 (2026-08-03). Data as JSON: /data/errors/8658a9c98287075a.json. Report an issue: GitHub.