{"record":{"id":"d02d5a54745e40f2","repo":"redis/node-redis","slug":"all-replies-must-be-numbers-for-sum-aggregation","errorCode":null,"errorMessage":"All replies must be numbers for sum aggregation","messagePattern":"All replies must be numbers for sum aggregation","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/client/lib/cluster/request-response-policies/generic-aggregators.ts","lineNumber":155,"sourceCode":"    return aggregateElementwise(replies, Math.max, 'max') as T;\n  }\n  if (!replies.every((reply): reply is number => typeof reply === 'number')) {\n    throw new Error('All replies must be numbers for max aggregation');\n  }\n  return Math.max(...replies) as T;\n};\n\n/**\n * Aggregates multiple numbers by finding the sum of all values.\n * @remarks\n * This implementation is specifically designed for Array<number> type only,\n * despite the generic type parameter. The generic type parameter T is provided\n * for usage ergonomy, but the actual input structure will be validated at runtime.\n */\nexport const aggregateSum = <T>(replies: Array<unknown>): T => {\n  if (replies.length === 0) return 0 as T;\n  if (!replies.every((reply): reply is number => typeof reply === 'number')) {\n    throw new Error('All replies must be numbers for sum aggregation');\n  }\n  return replies.reduce((acc, reply) => acc + reply, 0) as T;\n};\n\n\nexport const aggregateMerge = <T>(replies: Array<unknown>): T => {\n\tif(replies.length === 0) return undefined as T;\n\n\tconst firstReply = replies[0]\n\n\tif(Array.isArray(firstReply)) {\n\t\tconst set = new Set()\n\t\tfor(const reply of replies) {\n\t\t\tfor(const item of reply as Array<unknown>) {\n\t\t\t\tset.add(item);\n\t\t\t}\n\t\t}\n\t\treturn Array.from(set) as T;","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/redis/node-redis/blob/90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58/packages/client/lib/cluster/request-response-policies/generic-aggregators.ts#L137-L173","documentation":"Thrown by aggregateSum when at least one shard's reply is not a number. Used by DBSIZE (all_shards + agg_sum), DEL/EXISTS/TOUCH/UNLINK (multi_shard + agg_sum), and several MEMORY/LATENCY subcommands. Each shard must return a scalar number to be summed.","triggerScenarios":"DBSIZE or DEL under cluster mode where one shard returns a non-number reply; a multi_shard command where a sub-reply is not a number (e.g. one shard errors and the error reply resolves to a non-number); RESP decode interference.","commonSituations":"Mixed Redis versions where the command reply format differs; shard in a degraded state; type-mapping that converts numbers to strings in a way that bypasses the numeric-agg stripping.","solutions":["Ensure all cluster nodes run the same Redis version","Check for degraded or erroring shards","Verify the typeMapping configuration — a NUMBER:String mapping is normally stripped for numeric aggregations, but verify no custom path bypasses it"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isSumAggError(err: unknown): boolean {\n  return err instanceof Error && err.message.includes('sum aggregation');\n}","tryCatchPattern":"try {\n  await cluster.dbSize();\n} catch (err) {\n  if (err instanceof Error && err.message.includes('sum aggregation')) {\n    // a shard returned non-number — check node health and version\n  } else {\n    throw err;\n  }\n}","preventionTips":["Ensure all cluster nodes run the same Redis version","Monitor for degraded or erroring shards that return unexpected reply types","Verify the typeMapping is not bypassing the numeric-agg stripping logic"],"tags":["cluster","aggregation","dbsize","response-validation"],"backgroundTag":null,"analyzedSha":"90fd0652bc3f2a0a1b2f79fa9096b02a86b0ac58","analyzedAt":"2026-08-11T15:37:21.243Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}