n8n-io/n8n · error · InvalidLifecycleOptionsError
maxConcurrentPasses must be a positive integer, got ${maxCon
Error message
maxConcurrentPasses must be a positive integer, got ${maxConcurrentPasses} What it means
Thrown by createScheduler when lifecycleOptions.maxConcurrentPasses is not an integer >= 1. The value bounds how many scheduler passes (materializer/executor) may run at once; zero would never make progress, a fraction is meaningless for a count, and a negative value is invalid.
Source
Thrown at packages/@n8n/scheduler/src/core/factory.ts:183
if (!(Number.isFinite(value) && value > 0)) {
throw new InvalidLifecycleOptionsError(
`${key} must be a positive number of seconds, got ${value}`,
);
}
}
if (
lifecycleOptions.concurrencyMode !== 'sequential' &&
lifecycleOptions.concurrencyMode !== 'concurrent'
) {
throw new InvalidLifecycleOptionsError(
`concurrencyMode must be 'sequential' or 'concurrent', got ${String(lifecycleOptions.concurrencyMode)}`,
);
}
const { maxConcurrentPasses } = lifecycleOptions;
if (!(Number.isInteger(maxConcurrentPasses) && maxConcurrentPasses >= 1)) {
throw new InvalidLifecycleOptionsError(
`maxConcurrentPasses must be a positive integer, got ${maxConcurrentPasses}`,
);
}
const emit = (level: SchedulerEventLevel, message: string, context: Record<string, unknown>) => {
// The sink is the reporting channel itself: if it throws (a broken logger),
// there is nothing left to report through, and the pass that emitted must
// not be broken by its own observability.
try {
onEvent?.({ level, message, context });
} catch {
// Deliberately swallowed; see above.
}
};
const described = (error: unknown) => ensureError(error).message;
// Derived, not caller-chosen: the materializer must record a job's occurrences
// early enough that the executor still has them in hand when it needs to fire.View on GitHub (pinned to 5ac6606e81)
Solutions
- Set maxConcurrentPasses to an integer >= 1, e.g. Math.max(1, Math.floor(cpuCount)).
- Guard the CPU-count heuristic so it never returns below 1.
- Validate with Number.isInteger in your config loader.
- Omit the key to accept the DEFAULT_LIFECYCLE_OPTIONS value.
Example fix
// before
createScheduler({ lifecycle: { maxConcurrentPasses: os.cpus().length - 1 } }); // 0 on 1-core -> throws
// after - floor and clamp to a positive integer
const maxConcurrentPasses = Math.max(1, Math.floor(Number(process.env.SCHED_MAX_PASSES) || os.cpus().length));
createScheduler({ lifecycle: { maxConcurrentPasses } }); Defensive patterns
Strategy: validation
Validate before calling
function normalizeMaxPasses(raw: unknown): number {
const n = Math.floor(Number(raw));
if (!Number.isInteger(n) || n < 1) return 1;
return n;
}
const maxConcurrentPasses = normalizeMaxPasses(process.env.SCHED_MAX_PASSES ?? os.cpus().length); Type guard
function isPositiveInteger(v: unknown): v is number {
return typeof v === 'number' && Number.isInteger(v) && v >= 1;
} Prevention
- Clamp CPU-count heuristics with Math.max(1, ...) so a 1-core container cannot yield 0.
- Round before passing: floor any float that snuck in from a percentage computation.
- Unit-test the config loader with 0, 1.5, -1, NaN, undefined.
- Default to a known-safe integer when the source value is invalid.
When it happens
Trigger: Passing maxConcurrentPasses: 0, 1.5, -1, NaN, or a non-integer from a float config. Number.isInteger(maxConcurrentPasses) && maxConcurrentPasses >= 1 must hold.
Common situations: A CPU-count heuristic that returns 0 on a single-core/limited container (nproc - 1); a config expressed as a fraction of cores; NaN from parsing an unset env var; floats introduced by a percentage computation.
Related errors
- jitterRatio must be at least 0 and below 1, got ${lifecycleO
- ${key} must be a positive number of seconds, got ${value}
- concurrencyMode must be 'sequential' or 'concurrent', got ${
- interval.intervalSeconds must be a positive integer, got ${J
- recurring_cron.recurrenceUnit must be one of ${RecurringCron
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/42572751a1e2b32f.
Report an issue: GitHub.