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
- Wait for Sentinel to complete failover and re-run; this is often transient.
- Confirm Sentinel quorum and that a new master was actually promoted.
- Check the master's health directly and Sentinel's `SENTINEL master <name>` flags.
- 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
- Treat 'no valid master node' as transient during failover and retry with backoff.
- Ensure Sentinel quorum and replica promotion are configured so failover completes.
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
- Cannot find node
- couldn't connect to any sentinels
- No Replicas Nodes Enumerated
- None of the sentinels are available
- Scan iteration was interrupted by a Sentinel master change
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)