{"id":"2719b80d3c4fdb7d","repo":"redis/node-redis","slug":"all-replies-must-be-array-of-numbers-for-logical-a","errorCode":null,"errorMessage":"All replies must be array of numbers for logical AND aggregation","messagePattern":"All replies must be array of numbers for logical AND aggregation","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/client/lib/cluster/request-response-policies/generic-aggregators.ts","lineNumber":19,"sourceCode":"/**\n * Aggregates multiple arrays of numbers using logical AND operation.\n * @remarks\n * This implementation is specifically designed for Array<Array<number>> type only,\n * despite the generic type parameter. It is currently used by the SCRIPT EXISTS command\n * which returns an array of 0s and 1s from each shard.\n * The generic type parameter T is provided for usage ergonomy, but the actual input structure\n * will be validated at runtime.\n */\nexport const aggregateLogicalAnd = <T>(replies: Array<unknown>): T => {\n  if (replies.length === 0) return [] as T;\n  if (\n    !replies.every(\n      (reply): reply is number[] =>\n        Array.isArray(reply) &&\n        reply.every((value): value is number => typeof value === 'number')\n    )\n  ) {\n    throw new Error(\n      'All replies must be array of numbers for logical AND aggregation'\n    );\n  }\n\n  const result = Array(replies[0].length).fill(1);\n\n  for (const reply of replies) {\n    for (let i = 0; i < reply.length; i++) {\n      // clamp to 0/1: `&&` returns the operand, so non-binary replies would\n      // otherwise leak through (e.g. 1 && 2 === 2).\n      result[i] = result[i] && reply[i] ? 1 : 0;\n    }\n  }\n\n  return result as T;\n};\n\n/**","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/redis/node-redis/blob/bb5beb56578573910e2ee8f39681edc214c41398/packages/client/lib/cluster/request-response-policies/generic-aggregators.ts#L1-L37","documentation":"The `agg_logical_and` response reducer validates that every shard reply is an array of numbers (e.g. SCRIPT EXISTS returns [0,1,...] per shard) before folding column-wise. If any shard returned something that is not an Array<number>, the reducer refuses to aggregate rather than produce a silently-wrong boolean fold. This guards the runtime shape because the generic type parameter is ergonomics-only and the real input is validated at runtime.","triggerScenarios":"A fan-out command tagged `agg_logical_and` (currently SCRIPT EXISTS) where at least one node's reply is null, a non-array, or an array containing non-number elements. The dispatch already strips the caller's NUMBER typeMapping for numeric aggregators, so a plain `NUMBER: String` mapping will not cause this — the cause is a genuine shape change or an error reply leaking through.","commonSituations":"A Redis version where SCRIPT EXISTS returns a different reply shape on some node; a node returning an error that a custom typeMapping transformed into a non-number; mixing RESP2 and RESP3 nodes in a degraded cluster; a module reply-shape change after an upgrade.","solutions":["Check the Redis version parity across nodes — SCRIPT EXISTS reply shape must be consistent cluster-wide.","Inspect per-node replies by running SCRIPT EXISTS against each master directly to find the offending shape.","Avoid custom typeMapping entries that convert numbers to non-numbers for commands that aggregate.","Report a metadata/reply-shape regression if shapes diverge across versions."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isNumberArray(reply: unknown): reply is number[] {\n  return Array.isArray(reply) && reply.every(v => typeof v === 'number');\n}","tryCatchPattern":"try {\n  await cluster.sendCommand(['SCRIPT', 'EXISTS', sha1, sha2]);\n} catch (e) {\n  if (/array of numbers for logical AND/.test(e.message)) {\n    // a shard returned a non-conforming reply — check version parity / node health\n  } else throw e;\n}","preventionTips":["Keep Redis versions uniform across the cluster so SCRIPT EXISTS replies share a shape.","Avoid custom typeMapping entries that convert numbers to non-numbers for aggregated commands.","Monitor node health — error replies from a failing node can surface here."],"tags":["cluster","aggregation","response-policy","script-exists","reply-shape"],"analyzedSha":"bb5beb56578573910e2ee8f39681edc214c41398","analyzedAt":"2026-08-03T19:09:15.686Z","schemaVersion":2}