redis/node-redis · error · Error
SCAN: no master nodes available
Error message
SCAN: no master nodes available
What it means
Thrown by routeScan when a cluster-wide SCAN starts (cursor '0') but slots.nextScanTarget(EMPTY_VISITED) returns nothing, meaning the cluster topology currently exposes zero master nodes capable of serving the scan. The router cannot begin the per-node walk because it has nowhere to send the first SCAN.
Source
Thrown at packages/client/lib/cluster/request-response-policies/scan-cursor.ts:40
* finally sees "0".
*
* The visited set is tracked by node address, so a topology change mid-scan
* neither rescans a surviving node nor gets stuck on a departed one. The usual
* SCAN guarantees apply per node; keys migrating between nodes mid-iteration
* may be missed or duplicated — same caveat as every cluster-wide scan.
*/
export const routeScan: RequestRouter = async (slots, parser) => {
// Malformed raw command (missing cursor): forward to any node so the server
// returns its own arity error instead of a client-side TypeError.
if (parser.redisArgs.length < 2) {
return [{ client: await slots.nodeClient(slots.getRandomNode()) }];
}
const cursorArg = argToString(parser.redisArgs[1]);
if (cursorArg === '0') {
const address = slots.nextScanTarget(EMPTY_VISITED);
if (!address) throw new Error('SCAN: no master nodes available');
return [{ client: await pinnedMaster(slots, address) }];
}
const entry = slots.lookupScanCursor(cursorArg);
if (!entry) {
throw new Error(
`SCAN: unknown cursor "${cursorArg}". Cluster-wide SCAN cursors are ` +
`minted per client instance and expire when idle — restart the scan from 0.`
);
}
return [{
client: await pinnedMaster(slots, entry.address),
parser: withCursor(parser, entry.cursor)
}];
};
const EMPTY_VISITED: ReadonlySet<string> = new Set();
View on GitHub (pinned to bb5beb5657)
Solutions
- Ensure await client.connect() has resolved before issuing SCAN — check client.isReady.
- Retry the scan after a short delay if the cluster is mid-failover; start a fresh scan from cursor 0.
- Verify the cluster is healthy: run CLUSTER NODES / CLUSTER SLOTS against a seed node and confirm masters are online.
- Confirm you created the client with createCluster() pointing at real cluster nodes, not a single standalone Redis.
Example fix
// before const cluster = createCluster(...); cluster.scan(0); // topology not yet populated // after await cluster.connect(); await cluster.scan(0);
Defensive patterns
Strategy: validation
Validate before calling
if (!cluster.isReady) throw new Error('cluster not ready');
// Optionally probe topology first:
// const nodes = await cluster.clusterNodes(); Type guard
function clusterReady(c) { return typeof c.isReady === 'boolean' && c.isReady; } Try / catch
try { return await cluster.scan(cursor); } catch (e) { if (/no master nodes available/.test(e.message)) { await delay(500); return cluster.scan('0'); } throw e; } Prevention
- Always await client.connect() before issuing SCAN on a cluster.
- Check client.isReady before long scans.
- Monitor cluster health (CLUSTER NODES) before large key walks.
When it happens
Trigger: Calling client.scan(0) (or any scanIterator on a cluster client) while the cluster slots map is empty, still loading, or every shard is in a fail/downtime state. Also reachable if the cluster client is not yet connected/isReady when the first SCAN is issued.
Common situations: Issuing SCAN before await client.connect() resolves on a cluster; cluster in the middle of a full failover or resharding where the slots view is momentarily empty; misconfigured cluster where CLUSTER SLOTS returns no nodes; connecting to a non-cluster endpoint in cluster mode.
Related errors
- SCAN: node ${address} serving this cursor has left the clust
- Could not find shard
- Request policy ${requestPolicy} produced no target nodes
- Cannot find node ${address}
- FT.CURSOR: the node serving cursor ${token} on index "${argT
AI-assisted analysis of redis/node-redis@bb5beb5657 (2026-08-03).
Data as JSON: /data/errors/2110222c4fe05c5c.json.
Report an issue: GitHub.