ruvnet/ruflo · error

maxConcurrency must be a positive integer

Error message

maxConcurrency must be a positive integer

What it means

runBoundedPool validates its options before starting any work: options.maxConcurrency must be an integer >= 1 (Number.isInteger rejects 0, negatives, NaN, Infinity, and fractional values). The pool exists to make Codex/MetaHarness fanout deterministic and bounded, so an invalid concurrency limit is a caller bug and fails fast rather than degrading to unbounded execution.

Solutions

  1. Clamp and floor the value at the call site: const max = Math.max(1, Math.floor(Number(opts.concurrency) || DEFAULT))
  2. If deriving from tasks.length, guard the empty case explicitly before calling runBoundedPool
  3. Type the options object so maxConcurrency: number forces callers to resolve undefined/NaN earlier

Example fix

// before
await runBoundedPool(tasks, { maxConcurrency: tasks.length }); // throws when tasks is empty

// after
const maxConcurrency = Math.max(1, Math.floor(Number(options.concurrency ?? 4)));
const results = tasks.length
  ? await runBoundedPool(tasks, { maxConcurrency, timeoutMs: 30_000 })
  : { results: [], cancelled: false, elapsedMs: 0 };
Defensive patterns

Strategy: validation

Validate before calling

const raw = Number(config.concurrency); // from env/config
const maxConcurrency = Number.isInteger(raw) && raw >= 1 ? raw : 4; // safe default
await runBoundedPool(tasks, { maxConcurrency: Math.min(maxConcurrency, tasks.length || 1), timeoutMs: 30_000 });

Type guard

const isValidConcurrency = (v: unknown): v is number => typeof v === 'number' && Number.isInteger(v) && v >= 1;

Prevention

When it happens

Trigger: Passing tasks.length as maxConcurrency when tasks is empty (0), reading concurrency from an env var or config without parsing ('4' is not an integer, undefined from a missing key), or computing it with arithmetic that yields NaN/fractional values (e.g. tasks.length / 2 without Math.floor).

Common situations: Dynamic fanout where the task list can be empty, config-driven concurrency with a typo'd key, division-based heuristics like Math.round(n/3) that can round to 0 for small n.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/ea1cbc7172877816. Report an issue: GitHub.

Appendix: 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 fa13ee4ad6)