n8n-io/n8n · error · InvalidScheduleError

recurring_cron.recurrenceSize must be an integer of at least

Error message

recurring_cron.recurrenceSize must be an integer of at least 2 (a stride of 1 is a plain cron), got ${JSON.stringify(schedule.recurrenceSize)}

What it means

Thrown by validateRecurringCron when recurrenceSize is not an integer or is less than 2. The recurring_cron kind implements 'every N periods' semantics; N=1 would keep every cron fire, which is identical to a plain cron schedule, so the minimum is enforced at 2 to keep the schedule kinds semantically distinct.

Source

Thrown at packages/@n8n/scheduler/src/core/recurrence/kinds/recurring-cron.ts:35

/**
 * Checks an "every N periods" cron schedule: a valid cron expression, a known
 * period unit, and N of at least 2 (N = 1 keeps every fire, which is a plain
 * cron).
 * @param schedule The recurring_cron schedule to check.
 * @throws {InvalidScheduleError} When the cron, unit, or N is invalid.
 */
export function validateRecurringCron(schedule: RecurringCronSchedule): void {
	validateCron(schedule);

	if (!RecurringCronUnitList.includes(schedule.recurrenceUnit)) {
		throw new InvalidScheduleError(
			`recurring_cron.recurrenceUnit must be one of ${RecurringCronUnitList.join(', ')}, got ${JSON.stringify(schedule.recurrenceUnit)}`,
		);
	}

	if (!Number.isInteger(schedule.recurrenceSize) || schedule.recurrenceSize < 2) {
		throw new InvalidScheduleError(
			`recurring_cron.recurrenceSize must be an integer of at least 2 (a stride of 1 is a plain cron), got ${JSON.stringify(schedule.recurrenceSize)}`,
		);
	}
}

/**
 * Whether a candidate fire time should actually fire, for an "every N periods"
 * schedule. It only compares the candidate to the previous fire — there is no
 * hidden counter — so it gives the same answer on any machine and after a
 * restart.
 *
 * The candidate fires when either:
 * - it falls in the same period as the previous fire, so a rule like "every 2
 *   weeks on Monday and Wednesday" fires both days of a chosen week; or
 * - at least N periods have passed since the previous fire. "At least" (rather
 *   than "exactly") means that after downtime it resumes at the next fire
 *   instead of skipping a whole cycle.
 *

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Set recurrenceSize to an integer >= 2 (e.g. 2 for 'every 2 weeks', 3 for 'every 3 months').
  2. If you actually want every fire (N=1), switch the schedule kind to 'cron' instead of 'recurring_cron'.
  3. Validate recurrenceSize is an integer >= 2 before persisting the job row.
  4. Check the database row for the failing job and correct the recurrenceSize column.

Example fix

// before
const schedule = {
  kind: 'recurring_cron',
  cronExpression: '0 9 * * 1',
  recurrenceUnit: 'weeks',
  recurrenceSize: 1, // stride of 1 is a plain cron
};
// after — for every-other-week, keep recurring_cron
const schedule = {
  kind: 'recurring_cron',
  cronExpression: '0 9 * * 1',
  recurrenceUnit: 'weeks',
  recurrenceSize: 2,
};
// OR use plain cron for every-week
// const schedule = { kind: 'cron', cronExpression: '0 9 * * 1' };
Defensive patterns

Strategy: validation

Validate before calling

function isValidRecurrenceSize(value: unknown): boolean {
  return Number.isInteger(value) && (value as number) >= 2;
}

if (!isValidRecurrenceSize(schedule.recurrenceSize)) {
  throw new Error('recurrenceSize must be an integer >= 2');
}

Type guard

function isRecurrenceSize(value: unknown): value is number {
  return typeof value === 'number' && Number.isInteger(value) && value >= 2;
}

Try / catch

try {
  validateRecurringCron(schedule);
} catch (e) {
  if (e instanceof InvalidScheduleError && e.message.includes('recurrenceSize')) {
    // guide user to correct the value or switch to plain cron
    throw new UserError('Set recurrenceSize to 2 or higher, or use a plain cron schedule');
  }
  throw e;
}

Prevention

When it happens

Trigger: A recurring_cron schedule is registered with recurrenceSize set to 1, 0, a negative number, a non-integer (e.g. 2.5), NaN, undefined, or null. ResolveRecurringCron reads the value from the job row and validateRecurringCron checks Number.isInteger(schedule.recurrenceSize) and schedule.recurrenceSize >= 2.

Common situations: Mistakenly setting recurrenceSize to 1 when the intent was a plain weekly cron (should use kind='cron' instead). Storing a float from user input without rounding. A migration or seed script that defaults recurrenceSize to 1 or leaves it unset. JSON deserialization turning a missing field into undefined.

Related errors


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