redis/node-redis · error · Error

already attempting to open

Error message

already attempting to open

What it means

Thrown by RedisSentinelInternal.connect() when the `#isOpen` flag is already true. connect() is a one-shot lifecycle entry point: it sets #isOpen, drives #connect(), and only clears it in the finally block. Calling connect() a second time while the first is still in flight (or after a successful open) violates the state machine and is rejected immediately (sentinel/index.ts:987).

Source

Thrown at packages/client/lib/sentinel/index.ts:988

   *          if the client was immediately ready or no longer exists
   */
  releaseClientLease(clientInfo: ClientInfo) {
    const client = this.#masterClients[clientInfo.id];
    // client can be undefined if releasing in middle of a reconfigure
    if (client !== undefined) {
      const dirtyPromise = client.resetIfDirty();
      if (dirtyPromise) {
        return dirtyPromise
          .then(() => this.#masterClientQueue.push(clientInfo.id));
      }
    }

    this.#masterClientQueue.push(clientInfo.id);
  }

  async connect() {
    if (this.#isOpen) {
      throw new Error("already attempting to open")
    }

    try {
      this.#isOpen = true;

      this.#connectPromise = this.#connect();
      await this.#connectPromise;
      this.#isReady = true;
    } catch (err) {
      // The initial connect gave up. Tear down whatever was created along the
      // way: clients whose first connection attempt failed keep reconnecting
      // per their `reconnectStrategy`, and would otherwise stay alive in the
      // background (holding sockets and timers) after `connect()` rejected.
      this.#connectPromise = undefined;
      await this.destroy();
      throw err;
    } finally {
      this.#connectPromise = undefined;

View on GitHub (pinned to bb5beb5657)

Solutions

  1. Await the original connect() promise and store it; reuse it instead of calling connect() again.
  2. Guard with `if (!sentinel.isOpen) await sentinel.connect();` before connecting.
  3. If you need a fresh connection after an error, call destroy() first, then connect().
  4. Memoize the connect promise at the call site: `this._ready ??= sentinel.connect();`.

Example fix

// before
await sentinel.connect(); // may run twice on retry
// after
if (!sentinel.isOpen) {
  await sentinel.connect();
}
Defensive patterns

Strategy: validation

Validate before calling

let readyPromise: Promise<void> | undefined;
async function ensureOpen(sentinel) {
  if (sentinel.isOpen) return;
  readyPromise ??= sentinel.connect();
  try {
    await readyPromise;
  } finally {
    readyPromise = undefined;
  }
}

Type guard

const isOpen = (s: { isOpen: boolean }): boolean => s.isOpen;

Prevention

When it happens

Trigger: Calling sentinel.connect() twice without awaiting the first; calling connect() again after it already resolved; a reconnect/retry wrapper that invokes connect() on top of an already-open client.

Common situations: Retry/early-out wrappers around connect() that don't track the promise; calling connect() in both a bootstrap function and a health check; tests that reuse a client instance across cases.

Related errors


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