redis/node-redis · error · Error

All replies must be array of numbers for logical AND aggrega

Error message

All replies must be array of numbers for logical AND aggregation

What it means

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.

Source

Thrown at packages/client/lib/cluster/request-response-policies/generic-aggregators.ts:19

/**
 * Aggregates multiple arrays of numbers using logical AND operation.
 * @remarks
 * This implementation is specifically designed for Array<Array<number>> type only,
 * despite the generic type parameter. It is currently used by the SCRIPT EXISTS command
 * which returns an array of 0s and 1s from each shard.
 * The generic type parameter T is provided for usage ergonomy, but the actual input structure
 * will be validated at runtime.
 */
export const aggregateLogicalAnd = <T>(replies: Array<unknown>): T => {
  if (replies.length === 0) return [] as T;
  if (
    !replies.every(
      (reply): reply is number[] =>
        Array.isArray(reply) &&
        reply.every((value): value is number => typeof value === 'number')
    )
  ) {
    throw new Error(
      'All replies must be array of numbers for logical AND aggregation'
    );
  }

  const result = Array(replies[0].length).fill(1);

  for (const reply of replies) {
    for (let i = 0; i < reply.length; i++) {
      // clamp to 0/1: `&&` returns the operand, so non-binary replies would
      // otherwise leak through (e.g. 1 && 2 === 2).
      result[i] = result[i] && reply[i] ? 1 : 0;
    }
  }

  return result as T;
};

/**

View on GitHub (pinned to bb5beb5657)

Solutions

  1. Check the Redis version parity across nodes — SCRIPT EXISTS reply shape must be consistent cluster-wide.
  2. Inspect per-node replies by running SCRIPT EXISTS against each master directly to find the offending shape.
  3. Avoid custom typeMapping entries that convert numbers to non-numbers for commands that aggregate.
  4. Report a metadata/reply-shape regression if shapes diverge across versions.
Defensive patterns

Strategy: try-catch

Type guard

function isNumberArray(reply: unknown): reply is number[] {
  return Array.isArray(reply) && reply.every(v => typeof v === 'number');
}

Try / catch

try {
  await cluster.sendCommand(['SCRIPT', 'EXISTS', sha1, sha2]);
} catch (e) {
  if (/array of numbers for logical AND/.test(e.message)) {
    // a shard returned a non-conforming reply — check version parity / node health
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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