redis/node-redis · critical · Error

no valid master node

Error message

no valid master node

What it means

`analyze()` runs `parseNode` on the master record returned by Sentinel; if the record's flags mark the master as not usable (e.g. s_down, o_down, disconnected), parseNode returns undefined and analyze throws rather than acting on a master that cannot serve. It guards the failover decision step.

Solutions

  1. Wait for Sentinel to complete failover and re-run; this is often transient.
  2. Confirm Sentinel quorum and that a new master was actually promoted.
  3. Check the master's health directly and Sentinel's `SENTINEL master <name>` flags.
  4. Ensure replica promotion is enabled and replicas exist to fail over to.
Defensive patterns

Strategy: retry

Try / catch

async function waitForMaster(sentinel: { getMasterNode(): Promise<unknown> }, attempts = 10) {
  for (let i = 0; i < attempts; i++) {
    try { return await sentinel.getMasterNode(); }
    catch (e) {
      if (String(e).includes('no valid master node') && i < attempts - 1) {
        await new Promise(r => setTimeout(r, 1000 * (i + 1)));
        continue;
      }
      throw e;
    }
  }
  throw new Error('master never became valid');
}

Prevention

When it happens

Trigger: Sentinel returned a master record whose flags fail parseNode's validity check — typically because the master is reported down or a failover is mid-flight and no healthy master is elected yet.

Common situations: Failover in progress (old master flagged down, new not yet promoted); sentinel itself stale or split-brain; master crashed and no failover completed.

Related errors


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

Appendix: source

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

        this.#trace(`observe: error ${err}`);
        this.emit('error', err);
      } finally {
        if (client !== undefined && client.isOpen) {
          this.#trace(`observe: destroying sentinel client`);
          client.destroy();
        }
      }
    }

    this.#trace(`observe: none of the sentinels are available`);
    throw new Error('None of the sentinels are available');
  }

  analyze(observed: Awaited<ReturnType<RedisSentinelInternal<M, F, S, RESP, TYPE_MAPPING>["observe"]>>) {
    let master = parseNode(observed.masterData);
    if (master === undefined) {
      this.#trace(`analyze: no valid master node because ${observed.masterData.flags}`);
      throw new Error("no valid master node");
    }

    if (master.host === observed.currentMaster?.host && master.port === observed.currentMaster?.port) {
      this.#trace(`analyze: master node hasn't changed from ${observed.currentMaster?.host}:${observed.currentMaster?.port}`);
      master = undefined;
    } else {
      this.#trace(`analyze: master node has changed to ${master.host}:${master.port} from ${observed.currentMaster?.host}:${observed.currentMaster?.port}`);
    }

    let sentinel: RedisNode | undefined = observed.sentinelConnected;
    if (sentinel.host === observed.currentSentinel?.host && sentinel.port === observed.currentSentinel.port) {
      this.#trace(`analyze: sentinel node hasn't changed`);
      sentinel = undefined;
    } else {
      this.#trace(`analyze: sentinel node has changed to ${sentinel.host}:${sentinel.port}`);
    }

    const replicasToClose: Array<RedisNode> = [];

View on GitHub (pinned to 90fd0652bc)