ruvnet/ruflo · error · Error
maxConcurrency must be a positive integer
Error message
maxConcurrency must be a positive integer
What it means
runBoundedPool requires options.maxConcurrency to be a positive integer (Number.isInteger AND >= 1). Fractional, zero, negative, NaN, or undefined values throw before any task runs, so no partial work is started. The check is intentionally strict because the pool's correctness (deterministic order, cap enforcement) depends on an integer concurrency.
Source
Thrown at v3/@claude-flow/cli/src/services/bounded-worker-pool.ts:32
results: BoundedTaskResult<T>[];
peakConcurrency: number;
durationMs: number;
}
/**
* Deterministic bounded worker pool for Codex/MetaHarness fanout.
*
* Completion order never affects result order. The caller-supplied AbortSignal
* and timeout cancel both queued and cooperative running work. No unbounded
* Promise.all is used.
*/
export async function runBoundedPool<T>(
tasks: readonly BoundedTask<T>[],
options: { maxConcurrency: number; timeoutMs?: number; signal?: AbortSignal },
): Promise<BoundedPoolResult<T>> {
const started = Date.now();
if (!Number.isInteger(options.maxConcurrency) || options.maxConcurrency < 1) {
throw new Error('maxConcurrency must be a positive integer');
}
const ids = new Set<string>();
for (const task of tasks) {
if (!task.id || ids.has(task.id)) throw new Error(`duplicate or empty task id: ${task.id}`);
ids.add(task.id);
}
const maxConcurrency = Math.min(options.maxConcurrency, tasks.length || 1);
const controller = new AbortController();
const onAbort = () => controller.abort(options.signal?.reason ?? new Error('cancelled'));
options.signal?.addEventListener('abort', onAbort, { once: true });
const timer = options.timeoutMs && options.timeoutMs > 0
? setTimeout(() => controller.abort(new Error('worker-pool-timeout')), options.timeoutMs)
: undefined;
const results = new Map<string, BoundedTaskResult<T>>();
let cursor = 0;
let active = 0;
let peakConcurrency = 0;View on GitHub (pinned to 6b01dc5a68)
Solutions
- Compute concurrency defensively: Math.max(1, Math.floor(value)) and validate isFinite first.
- Validate env-derived concurrency before passing it in.
- Pick a sane default (e.g., os.cpus().length) when the configured value is invalid.
Example fix
// before
const pool = await runBoundedPool(tasks, { maxConcurrency: Number(process.env.WORKERS) });
// WORKERS unset -> NaN -> throws
// after
const raw = Number(process.env.WORKERS);
const maxConcurrency = Number.isFinite(raw) && raw >= 1 ? Math.floor(raw) : 4;
const pool = await runBoundedPool(tasks, { maxConcurrency, timeoutMs: 30000 }); Defensive patterns
Strategy: validation
Validate before calling
function safeConcurrency(raw, fallback = 4) {
const n = Number(raw);
return Number.isInteger(n) && n >= 1 ? n : fallback;
} Type guard
function isPositiveInt(n): n is number {
return typeof n === 'number' && Number.isInteger(n) && n >= 1;
} Prevention
- Never pass raw env-derived numbers straight into options; coerce and validate first.
- Default to a known good value (os.cpus().length) when parsing fails.
- Beware Number(undefined) === NaN and Number('') === 0.
When it happens
Trigger: Calling runBoundedPool(tasks, { maxConcurrency: 0 }), with a float like 2.5, with NaN (often from a bad parseInt), or omitting the option so it is undefined.
Common situations: Reading concurrency from an env var without validation (Number('') === 0, Number(undefined) === NaN); computing maxConcurrency as tasks.length / workers without Math.ceil; defaulting to 0 when no workers configured.
Related errors
- Invalid route entry: ${JSON.stringify(r)}
- Duplicate route name: ${r.name}
- mode must be legacy, observe, or enforce
- Key exceeds maximum nesting depth of ${MAX_NESTING_DEPTH}
- Dangerous key segment rejected: ${part}
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/ea1cbc7172877816.
Report an issue: GitHub.