n8n-io/n8n · error · InvalidScheduleError

interval.intervalSeconds must be a positive integer, got ${J

Error message

interval.intervalSeconds must be a positive integer, got ${JSON.stringify(schedule.intervalSeconds)}

What it means

Thrown by validateInterval when schedule.intervalSeconds is not an integer, or is <= 0. Intervals count whole seconds between fires; a fractional second is not representable, and a non-positive interval would either never fire or hot-loop.

Source

Thrown at packages/@n8n/scheduler/src/core/recurrence/kinds/interval.ts:14

import { Time } from '@n8n/constants';

import { InvalidScheduleError } from '../../errors';
import type { IntervalSchedule, ScheduledJob } from '../../types';
import { required } from '../field';

/**
 * Checks that an interval schedule has a positive whole number of seconds.
 * @param schedule The interval schedule to check.
 * @throws {InvalidScheduleError} When the interval is not a positive integer.
 */
export function validateInterval(schedule: IntervalSchedule): void {
	if (!Number.isInteger(schedule.intervalSeconds) || schedule.intervalSeconds <= 0) {
		throw new InvalidScheduleError(
			`interval.intervalSeconds must be a positive integer, got ${JSON.stringify(schedule.intervalSeconds)}`,
		);
	}
}

/**
 * The next interval fire after `after`: `after` plus the interval. Counts real
 * elapsed time, so daylight-saving changes never shift a fire.
 * @param schedule The interval schedule.
 * @param after The previous occurrence.
 * @returns The next fire time.
 */
export function intervalNextRun(schedule: IntervalSchedule, after: Date): Date {
	return new Date(after.getTime() + schedule.intervalSeconds * Time.seconds.toMilliseconds);
}

/**
 * Every interval fire starting at `first`, oldest first.

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Set intervalSeconds to a positive integer (e.g. 60 for one minute).
  2. For sub-second scheduling, use a different mechanism - the interval scheduler only supports whole seconds.
  3. Round before passing: Math.max(1, Math.round(seconds)).
  4. Validate with Number.isInteger in your config loader.

Example fix

// before
validateInterval({ kind: 'interval', intervalSeconds: 0.5 }); // throws - not an integer

// after - coerce to a positive whole second
const intervalSeconds = Math.max(1, Math.round(Number(userInput) || 60));
validateInterval({ kind: 'interval', intervalSeconds });
Defensive patterns

Strategy: validation

Validate before calling

function normalizeIntervalSeconds(raw: unknown): number {
  const n = Math.floor(Number(raw));
  if (!Number.isInteger(n) || n <= 0) {
    throw new Error('intervalSeconds must be a positive integer (sub-second scheduling is not supported)');
  }
  return n;
}

Type guard

function isPositiveIntegerSeconds(v: unknown): v is number {
  return typeof v === 'number' && Number.isInteger(v) && v > 0;
}

Prevention

When it happens

Trigger: An interval schedule with intervalSeconds: 0, -5, 1.5, NaN, or Infinity. Number.isInteger(intervalSeconds) && intervalSeconds > 0 must hold.

Common situations: User enters 0.5 seconds intending 500ms (sub-second intervals are not supported - use cron or a different mechanism); a config that left intervalSeconds unset and defaulted to 0; a float from a unit conversion (minutes -> seconds with rounding); NaN from parseFloat on a non-numeric env var.

Related errors


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