{"record":{"id":"5f79f25d636fce48","repo":"redis/node-redis","slug":"request-policy-requestpolicy-produced-no-target","errorCode":null,"errorMessage":"Request policy ${requestPolicy} produced no target nodes","messagePattern":"Request policy (.+?) produced no target nodes","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/client/lib/cluster/index.ts","lineNumber":589,"sourceCode":"    }\n    // Validated before any per-node promise is dispatched — throwing after\n    // would orphan in-flight rejections (unhandled-rejection noise) and run\n    // side effects for a reply that can never be reduced.\n    const reducer = RESPONSE_REDUCERS[responsePolicy];\n    if (!reducer) {\n      throw new Error(`Unknown response policy ${responsePolicy}`);\n    }\n    // Routers are typed against the erased base cluster types (routing is\n    // below the typed command surface); bridge this instantiation's slots in.\n    const plan = await router(\n      this._slots as unknown as Parameters<typeof router>[0],\n      parser,\n      readonly,\n      policy.keySpecs\n    );\n\n    if (plan.length === 0) {\n      throw new Error(`Request policy ${requestPolicy} produced no target nodes`);\n    }\n\n    // Numeric aggregation must see raw numbers: strip the caller's type\n    // mapping from the per-node executions (a `NUMBER: String` mapping would\n    // feed strings into the numeric reducers) and re-apply it to the\n    // aggregated result below. Pass-through policies keep the mapping — their\n    // replies reach the caller undisturbed.\n    const numericAgg = NUMERIC_AGG_POLICIES.has(responsePolicy);\n    const requestedMapping = options?.typeMapping;\n    const execOptions = numericAgg && requestedMapping\n      ? { ...options, typeMapping: undefined }\n      : options;\n\n    // Track the actually-serving client for single-target plans: after a\n    // MOVED/ASK redirect the reply comes from a different node than\n    // `plan[0].client`, and the post-reply hooks must bind cursors to the node\n    // that really served. Multi-target hooks are no-ops, so nothing to track.\n    const served: { client?: RedisClientType<M, F, S, RESP, TYPE_MAPPING> } = {};","sourceCodeStart":571,"sourceCodeEnd":607,"githubUrl":"https://github.com/redis/node-redis/blob/90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58/packages/client/lib/cluster/index.ts#L571-L607","documentation":"Thrown when a cluster command's request-policy router returns an empty execution plan — zero target nodes were selected. This occurs for all_nodes/all_shards policies (e.g. DBSIZE, WAIT, SCRIPT EXISTS, FLUSHALL) when the cluster's slot table contains no known master nodes, meaning topology discovery has not completed or every node is unreachable.","triggerScenarios":"Calling an all_shards or all_nodes command (DBSIZE, WAIT, WAITAOF, SCRIPT EXISTS, SCRIPT LOAD, MEMORY PURGE, CLIENT NO-EVICT) before cluster.connect() resolves, or when all seed nodes are down and slot discovery has failed. The routers routeAllNodes/routeAllShards call slots.getAllNodes()/getAllMasterNodes() which return empty arrays when the topology is empty.","commonSituations":"Application starts issuing commands before awaiting connect(); cluster-wide outage or network partition where all rootNodes are unreachable; race condition during failover where the slot table is temporarily empty after rediscovery.","solutions":["Ensure `await cluster.connect()` completes before issuing any commands","Verify all configured rootNodes are reachable and healthy","Implement reconnection logic with exponential backoff and retry the command after slots are re-discovered","Check for RootNodesUnavailableError during connect() — if it fires, the cluster never established a topology"],"exampleFix":"// before\nconst cluster = createCluster({ rootNodes: ['redis://node-1:6379'] });\nawait cluster.dbSize(); // throws: produced no target nodes\n\n// after\nconst cluster = createCluster({ rootNodes: ['redis://node-1:6379'] });\nawait cluster.connect();\nawait cluster.dbSize(); // works","handlingStrategy":"validation","validationCode":"// Check that the cluster has discovered nodes before issuing\n// all_shards / all_nodes commands.\nimport { commandExists } from './cluster-health';\n\nasync function assertClusterReady(cluster: RedisClusterType) {\n  const nodes = cluster.nodes('master');\n  if (nodes.length === 0) {\n    throw new Error('Cluster has no master nodes — call await cluster.connect() first');\n  }\n}\n\n// call before all_shards/all_nodes commands\nawait assertClusterReady(cluster);\nawait cluster.dbSize();","typeGuard":null,"tryCatchPattern":"try {\n  await cluster.dbSize();\n} catch (err) {\n  if (err instanceof Error && err.message.includes('produced no target nodes')) {\n    // topology not yet established — reconnect and retry\n    await cluster.connect();\n    await cluster.dbSize();\n  } else {\n    throw err;\n  }\n}","preventionTips":["Always await cluster.connect() before issuing any commands","Implement a health-check wrapper that verifies node availability before all_shards/all_nodes commands","Use exponential backoff reconnection logic for transient cluster unavailability"],"tags":["cluster","connectivity","routing","topology"],"backgroundTag":null,"analyzedSha":"90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58","analyzedAt":"2026-08-11T15:37:21.243Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}