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

  1. Set lifecycle.jitterRatio to a number in [0, 1), e.g. 0.25 for +/-25% jitter.
  2. If your config is a percentage, divide by 100 before passing: jitterRatio: pct / 100.
  3. Coerce with Number() and validate with Number.isFinite before calling createScheduler.
  4. 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

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


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/260e9d69e3f1f105. Report an issue: GitHub.