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

  1. Ensure `await cluster.connect()` has resolved before issuing any command, especially fan-out ones.
  2. Verify at least one seed node in `rootNodes`/`nodeAddressMap` is reachable and is a cluster master.
  3. If hit mid-run, retry after a short delay to let `rediscover` repopulate the slot map from a live seed.
  4. 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

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


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