redis/node-redis · error · Error
Request policy ${requestPolicy} produced no target nodes
Error message
Request policy ${requestPolicy} produced no target nodes What it means
Thrown after a request-policy router runs but returns an empty plan (zero target nodes). The fan-out routers build the plan from the live slots topology, so an empty plan means RedisClusterSlots currently knows of no usable nodes. The client refuses to dispatch a fan-out command to nothing rather than silently returning an empty/trivial aggregate. This is a topology-state precondition, not a bug in the command itself.
Source
Thrown at packages/client/lib/cluster/index.ts:589
}
// Validated before any per-node promise is dispatched — throwing after
// would orphan in-flight rejections (unhandled-rejection noise) and run
// side effects for a reply that can never be reduced.
const reducer = RESPONSE_REDUCERS[responsePolicy];
if (!reducer) {
throw new Error(`Unknown response policy ${responsePolicy}`);
}
// Routers are typed against the erased base cluster types (routing is
// below the typed command surface); bridge this instantiation's slots in.
const plan = await router(
this._slots as unknown as Parameters<typeof router>[0],
parser,
readonly,
policy.keySpecs
);
if (plan.length === 0) {
throw new Error(`Request policy ${requestPolicy} produced no target nodes`);
}
// Numeric aggregation must see raw numbers: strip the caller's type
// mapping from the per-node executions (a `NUMBER: String` mapping would
// feed strings into the numeric reducers) and re-apply it to the
// aggregated result below. Pass-through policies keep the mapping — their
// replies reach the caller undisturbed.
const numericAgg = NUMERIC_AGG_POLICIES.has(responsePolicy);
const requestedMapping = options?.typeMapping;
const execOptions = numericAgg && requestedMapping
? { ...options, typeMapping: undefined }
: options;
// Track the actually-serving client for single-target plans: after a
// MOVED/ASK redirect the reply comes from a different node than
// `plan[0].client`, and the post-reply hooks must bind cursors to the node
// that really served. Multi-target hooks are no-ops, so nothing to track.
const served: { client?: RedisClientType<M, F, S, RESP, TYPE_MAPPING> } = {};View on GitHub (pinned to bb5beb5657)
Solutions
- Ensure `await cluster.connect()` has resolved before issuing any command, especially fan-out ones.
- Verify at least one seed node in `rootNodes`/`nodeAddressMap` is reachable and is a cluster master.
- If hit mid-run, retry after a short delay to let `rediscover` repopulate the slot map from a live seed.
- Confirm the client instance is not reused after `.disconnect()`/`.close()`.
Example fix
// before
const cluster = createCluster({ rootNodes: [{ socket: { host: '127.0.0.1', port: 7000 } }] });
await cluster.sendCommand(['SCRIPT', 'KILL']); // throws: produced no target nodes
// after
await cluster.connect();
await cluster.sendCommand(['SCRIPT', 'KILL']); Defensive patterns
Strategy: validation
Validate before calling
// Guard fan-out commands against an empty topology before dispatch.
async function assertClusterReady(cluster) {
if (!cluster.isOpen) await cluster.connect();
// trigger a cheap topology check via a keyless round-trip
await cluster.sendCommand(['PING']);
}
await assertClusterReady(cluster);
await cluster.sendCommand(['SCRIPT', 'KILL']); Try / catch
try {
await cluster.sendCommand(['SCRIPT', 'KILL']);
} catch (e) {
if (/produced no target nodes/.test(e.message)) {
// topology not populated — reconnect or wait for rediscovery
await cluster.connect();
throw e; // surface to caller for retry decision
}
throw e;
} Prevention
- Always `await cluster.connect()` before issuing commands and treat it as a precondition.
- Do not reuse a cluster client after disconnect()/close().
- For fan-out (all_nodes/all_shards) commands, ensure the cluster has at least one reachable master.
- Wire a top-level 'not connected' guard in your data-access layer.
When it happens
Trigger: Issuing a command whose request policy is `all_nodes` or `all_shards` (e.g. SCRIPT KILL under one_succeeded, or any all-shards fan-out) when `RedisClusterSlots.getAllNodes()`/`getAllMasterNodes()` returns []. Also reachable if a rediscovery cycle evicted every node and has not repopulated, or a command is issued against a cluster that was never connected or has been disconnected.
Common situations: Awaiting a fan-out command before `await cluster.connect()` resolves; starting the cluster against a seed list where every seed is down; issuing commands after `cluster.disconnect()`; a topology refresh racing during a full cluster outage where the slot map is transiently empty.
Related errors
- Cannot find node ${address}
- Cluster closed
- All the root nodes are unavailable
- Could not find shard
- Unknown request policy ${requestPolicy}
AI-assisted analysis of redis/node-redis@bb5beb5657 (2026-08-03).
Data as JSON: /data/errors/5f79f25d636fce48.json.
Report an issue: GitHub.