n8n-io/n8n · error · InvalidLifecycleOptionsError
${key} must be a positive number of seconds, got ${value}
Error message
${key} must be a positive number of seconds, got ${value} What it means
Thrown by createScheduler when any of eight lifecycle duration options (materializer/executor/reaper/retention Interval*Seconds and Timeout*Seconds) is not a positive finite number. The comment above the loop explains why: a non-positive or NaN interval collapses to setTimeout's 1ms floor and turns the scheduler pass into a hot loop against task storage; a non-positive timeout abandons every pass immediately.
Source
Thrown at packages/@n8n/scheduler/src/core/factory.ts:166
}
// A non-positive or NaN interval collapses to setTimeout's 1ms floor,
// turning the pass into a hot loop against task storage.
// A non-positive or NaN timeout would abandon every pass the moment it starts.
const durationKeys = [
'materializerIntervalSeconds',
'executorIntervalSeconds',
'reaperIntervalSeconds',
'retentionIntervalSeconds',
'materializerTimeoutSeconds',
'executorTimeoutSeconds',
'reaperTimeoutSeconds',
'retentionTimeoutSeconds',
] as const;
for (const key of durationKeys) {
const value = lifecycleOptions[key];
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}`,View on GitHub (pinned to 5ac6606e81)
Solutions
- Set each interval and timeout to a positive finite number of seconds (e.g. materializerIntervalSeconds: 1, executorTimeoutSeconds: 30).
- Express 'no timeout' as a large finite number, never Infinity.
- Validate with Number.isFinite(x) && x > 0 in your config loader before createScheduler.
- Let withDefaults fill values by omitting the keys entirely rather than passing 0 or undefined.
Example fix
// before
createScheduler({ lifecycle: { materializerIntervalSeconds: 0, executorTimeoutSeconds: NaN } }); // throws
// after - sanitize config-derived values
function positiveSeconds(raw: unknown, fallback: number): number {
const n = Number(raw);
return Number.isFinite(n) && n > 0 ? n : fallback;
}
createScheduler({
lifecycle: {
materializerIntervalSeconds: positiveSeconds(cfg.materInterval, 1),
executorTimeoutSeconds: positiveSeconds(cfg.execTimeout, 30),
},
}); Defensive patterns
Strategy: validation
Validate before calling
const DURATION_KEYS = [
'materializerIntervalSeconds', 'executorIntervalSeconds',
'reaperIntervalSeconds', 'retentionIntervalSeconds',
'materializerTimeoutSeconds', 'executorTimeoutSeconds',
'reaperTimeoutSeconds', 'retentionTimeoutSeconds',
] as const;
function sanitizeDurations(opts: Record<string, unknown>): Record<string, number> {
const out: Record<string, number> = {};
for (const key of DURATION_KEYS) {
const n = Number(opts[key]);
if (!(Number.isFinite(n) && n > 0)) throw new Error(`${key} must be a positive finite number`);
out[key] = n;
}
return out;
} Type guard
function isPositiveSeconds(v: unknown): v is number {
return typeof v === 'number' && Number.isFinite(v) && v > 0;
} Prevention
- Treat 'no timeout' as a large finite number, never Infinity.
- Validate env-var-derived values with Number.isFinite && > 0 before passing to createScheduler.
- Let withDefaults fill values by omitting keys rather than passing 0 or undefined.
- Unit-test the config loader with empty strings, undefined, and Infinity.
When it happens
Trigger: Passing lifecycle.materializerIntervalSeconds: 0, a negative number, NaN, Infinity, or undefined-after-defaults. Each of the eight keys is checked with Number.isFinite(value) && value > 0.
Common situations: An env var that is empty string (Number('') === 0); a config that omits a timeout so it defaults to undefined and withDefaults leaves it undefined; passing Infinity from a 'never timeout' intent; floats that drift to 0 after integer casting upstream.
Related errors
- jitterRatio must be at least 0 and below 1, got ${lifecycleO
- maxConcurrentPasses must be a positive integer, got ${maxCon
- 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/56d9957fe186b4a8.
Report an issue: GitHub.