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
- Set intervalSeconds to a positive integer (e.g. 60 for one minute).
- For sub-second scheduling, use a different mechanism - the interval scheduler only supports whole seconds.
- Round before passing: Math.max(1, Math.round(seconds)).
- 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
- Round before passing: Math.max(1, Math.round(Number(raw))).
- For sub-second scheduling, use a different mechanism - the interval scheduler only supports whole seconds.
- Reject fractional and zero values in the UI with a clear message about whole seconds.
- Unit-test the config loader with 0, 0.5, NaN, undefined.
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
- jitterRatio must be at least 0 and below 1, got ${lifecycleO
- ${key} must be a positive number of seconds, got ${value}
- maxConcurrentPasses must be a positive integer, got ${maxCon
- ${toolName} policy.maxChildren must be a finite positive int
- ${toolName} policy.maxChildren must be at least 1
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/521a6727e726fed9.
Report an issue: GitHub.