n8n-io/n8n · error · InvalidLifecycleOptionsError
jitterRatio must be at least 0 and below 1, got ${lifecycleO
Error message
jitterRatio must be at least 0 and below 1, got ${lifecycleOptions.jitterRatio} What it means
Thrown by the scheduler factory (createScheduler) when lifecycleOptions.jitterRatio, after applying DEFAULT_LIFECYCLE_OPTIONS defaults, is not in [0, 1). jitterRatio spreads materializer/executor wake-ups to avoid thundering-herd against task storage; a value >= 1 or negative makes jitter arithmetic produce negative or huge delays.
Source
Thrown at packages/@n8n/scheduler/src/core/factory.ts:145
* retention passes bound to their options, with every incident routed to
* `onEvent` as a described {@link SchedulerEvent}. `start` runs each pass on
* its own jittered {@link Loop}; `stop` drains them.
*/
export function createScheduler(deps: SchedulerDeps): Scheduler & SchedulerPasses {
const { hostId, materializerTransaction, taskStore, onEvent } = deps;
const tracer = deps.tracer ?? noopTracer;
const metrics = deps.metrics ?? noopMetrics;
const materializerOptions = withDefaults(DEFAULT_MATERIALIZER_OPTIONS, deps.materializer);
const executorOptions = withDefaults(DEFAULT_EXECUTOR_OPTIONS, deps.executor);
const reaperOptions = withDefaults(DEFAULT_REAPER_OPTIONS, deps.reaper);
const retentionOptions = withDefaults(DEFAULT_RETENTION_OPTIONS, deps.retention);
const lifecycleOptions = withDefaults(DEFAULT_LIFECYCLE_OPTIONS, deps.lifecycle);
const clockSkewOptions = withDefaults(DEFAULT_CLOCK_SKEW_OPTIONS, deps.clockSkew);
const dispatchLagWarnThresholdSeconds =
deps.dispatchLagWarnThresholdSeconds ?? DEFAULT_DISPATCH_LAG_WARN_THRESHOLD_SECONDS;
if (!(lifecycleOptions.jitterRatio >= 0 && lifecycleOptions.jitterRatio < 1)) {
throw new InvalidLifecycleOptionsError(
`jitterRatio must be at least 0 and below 1, got ${lifecycleOptions.jitterRatio}`,
);
}
// 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) {View on GitHub (pinned to 5ac6606e81)
Solutions
- Set lifecycle.jitterRatio to a number in [0, 1), e.g. 0.25 for +/-25% jitter.
- If your config is a percentage, divide by 100 before passing: jitterRatio: pct / 100.
- Coerce with Number() and validate with Number.isFinite before calling createScheduler.
- Omit jitterRatio to accept the DEFAULT_LIFECYCLE_OPTIONS value.
Example fix
// before
const scheduler = createScheduler({ lifecycle: { jitterRatio: 25, ... } }); // throws
// after - normalize percentage to ratio, clamp into range
const pct = Number(process.env.SCHED_JITTER_PCT ?? 25);
const jitterRatio = Math.min(Math.max(pct / 100, 0), 0.999);
const scheduler = createScheduler({ lifecycle: { jitterRatio, ... } }); Defensive patterns
Strategy: validation
Validate before calling
function normalizeJitter(raw: unknown): number {
const n = Number(raw);
if (!Number.isFinite(n)) return 0.25; // default fallback
// accept either ratio (0..1) or percentage (0..100)
const ratio = n > 1 ? n / 100 : n;
return Math.min(Math.max(ratio, 0), 0.999);
}
const jitterRatio = normalizeJitter(process.env.SCHED_JITTER);
if (!(jitterRatio >= 0 && jitterRatio < 1)) {
throw new Error('jitterRatio out of range');
} Type guard
function isValidJitterRatio(n: unknown): n is number {
return typeof n === 'number' && Number.isFinite(n) && n >= 0 && n < 1;
} Prevention
- Decide upfront whether your config expresses jitter as ratio (0..1) or percentage (0..100) and normalize at the boundary.
- Clamp into [0, 0.999] in the config loader so a boundary value of 1 cannot reach createScheduler.
- Reject NaN explicitly (Number.isFinite does this).
- Document the unit next to the config key.
When it happens
Trigger: Calling createScheduler({ lifecycle: { jitterRatio: 1 } }) (boundary exclusive), jitterRatio: -0.1, or NaN (NaN fails both >= 0 and < 1). Also triggered if a config loader coerces a percentage (e.g. 50 for 50%) into 50 instead of 0.5.
Common situations: Migrating from a config that expressed jitter as a percentage (0-100) instead of a ratio (0-1); a typo; NaN slipped in from parseFloat(undefined); env var override that drops the decimal point.
Related errors
- ${key} must be a positive number of seconds, got ${value}
- 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/260e9d69e3f1f105.
Report an issue: GitHub.