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

  1. Set each interval and timeout to a positive finite number of seconds (e.g. materializerIntervalSeconds: 1, executorTimeoutSeconds: 30).
  2. Express 'no timeout' as a large finite number, never Infinity.
  3. Validate with Number.isFinite(x) && x > 0 in your config loader before createScheduler.
  4. 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

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


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