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.
Solutions
- Pass a positive integer explicitly, or omit the argument to use MAX_REDIS_PIPELINE_COMMANDS (1000)
- Fix the config/env source: coerce and validate with Number.isInteger before calling, and provide a sane fallback
- If the value is computed, clamp it: maxCommands = Math.max(1, Math.floor(raw))
- 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
- Validate any config/env-derived batch size with Number.isInteger and > 0 before use
- Clamp computed values: Math.max(1, Math.floor(raw))
- Rely on the default (MAX_REDIS_PIPELINE_COMMANDS = 1000) unless you have a reason
- Unit-test chunkRedisCommands with 0, -1, NaN, and fractional inputs
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
- Invalid GEOSEARCH result
- Physical divergence snapshot must contain gold and silver…
- Redis transaction failed
- a pull_request payload is required
- A valid country code is required for a resilience score…
AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-01).
Data as JSON: /api/errors/cea3dd6678638f0e.
Report an issue: GitHub.
Appendix: source
Thrown at server/worldmonitor/news/v1/list-feed-digest.ts:1658
'HMGET', STORY_TRACK_KEY(h), ...fields,
]);
const timeoutMs = redisTimeoutForDeadline(deadlineAt);
if (timeoutMs === undefined) return new Map();
const results = await runRedisPipeline(commands, false, timeoutMs);
const map = new Map<string, StoryTrack>();
for (let i = 0; i < titleHashes.length; i++) {
const vals = results[i]?.result as string[] | null;
if (!vals || !vals[0]) continue; // firstSeen missing → new story
map.set(titleHashes[i]!, {
firstSeen: Number(vals[0]),
lastSeen: Number(vals[1] ?? 0),
mentionCount: Number(vals[2] ?? 0),
sourceCount: Number(vals[3] ?? 0),
});
}
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));View on GitHub (pinned to e586b8b4b8)