koala73/worldmonitor · error · RangeError

maxCommands must be a positive integer

Error message

maxCommands must be a positive integer

What it means

chunkRedisCommands splits an array of Redis pipeline commands into chunks of at most maxCommands (default MAX_REDIS_PIPELINE_COMMANDS = 1000). It throws a RangeError when maxCommands is not an integer or is less than 1, because a non-positive chunk size would produce an infinite/invalid loop or empty chunks. This is an argument-validation guard on a helper used when writing feed-digest data to Redis.

Source

Thrown at server/worldmonitor/news/v1/list-feed-digest.ts:1599

    });
  }
  return map;
}

function parseRedisTimestamp(value: unknown): number | undefined {
  if (value === null || value === undefined) return undefined;
  if (typeof value !== 'string' && typeof value !== 'number') return undefined;
  if (typeof value === 'string' && value.trim().length === 0) return undefined;
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : undefined;
}

function chunkRedisCommands(
  commands: Array<Array<string | number>>,
  maxCommands: number = MAX_REDIS_PIPELINE_COMMANDS,
): Array<Array<Array<string | number>>> {
  if (!Number.isInteger(maxCommands) || maxCommands < 1) {
    throw new RangeError('maxCommands must be a positive integer');
  }
  const chunks: Array<Array<Array<string | number>>> = [];
  for (let offset = 0; offset < commands.length; offset += maxCommands) {
    chunks.push(commands.slice(offset, offset + maxCommands));
  }
  return chunks;
}

/**
 * Convert an absolute digest deadline into a Redis request timeout. A caller
 * must not start a request after its deadline, but a short positive remainder
 * is still useful because same-region Upstash reads normally complete far
 * below the shared five-second fallback timeout.
 */
function redisTimeoutForDeadline(deadlineAt: number): number | undefined {
  const remainingMs = deadlineAt - Date.now();
  if (remainingMs <= 0) return undefined;
  return Math.min(REDIS_PIPELINE_TIMEOUT_MS, Math.max(1, Math.ceil(remainingMs)));

View on GitHub (pinned to 9361220cc0)

Solutions

  1. Pass a positive integer explicitly, or omit the argument to use MAX_REDIS_PIPELINE_COMMANDS (1000)
  2. Fix the config/env source: coerce and validate with Number.isInteger before calling, and provide a sane fallback
  3. If the value is computed, clamp it: maxCommands = Math.max(1, Math.floor(raw))
  4. Add a unit test covering 0, negative, and NaN inputs to lock the validation behavior

Example fix

// before
chunkRedisCommands(cmds, Number(env.PIPELINE_BATCH)) // NaN
// after
const batch = Number(env.PIPELINE_BATCH);
chunkRedisCommands(cmds, Number.isInteger(batch) && batch > 0 ? batch : MAX_REDIS_PIPELINE_COMMANDS)
Defensive patterns

Strategy: validation

Validate before calling

function assertPositiveInt(n) {
  if (!Number.isInteger(n) || n < 1) throw new RangeError(`maxCommands must be a positive integer, got ${n}`);
  return n;
}
chunkRedisCommands(cmds, assertPositiveInt(cfg.maxCommands));

Type guard

function isPositiveInt(v: unknown): v is number {
  return typeof v === 'number' && Number.isInteger(v) && v >= 1;
}

Try / catch

try {
  chunks = chunkRedisCommands(cmds, maxCommands);
} catch (e) {
  if (e instanceof RangeError) chunks = chunkRedisCommands(cmds); // fall back to default 1000
  else throw e;
}

Prevention

When it happens

Trigger: Calling chunkRedisCommands with an explicit maxCommands argument that is 0, negative, non-integer (e.g. NaN, 0.5), or a value derived from config/env math that evaluates to NaN.

Common situations: Env/config variable missing so Number(...) yields NaN; a computed limit like Math.floor(total/buckets) reaching 0; refactoring changed the default constant to a misparsed value; a caller passing user-supplied batch size unvalidated.

Related errors


AI-assisted analysis of koala73/worldmonitor@9361220cc0 (2026-09-01). Data as JSON: /api/errors/cea3dd6678638f0e. Report an issue: GitHub.