n8n-io/n8n · error · InvalidScheduleError
No fire of cron expression ${JSON.stringify(schedule.cronExp
Error message
No fire of cron expression ${JSON.stringify(schedule.cronExpression)} lands on the every-${schedule.recurrenceSize}-${schedule.recurrenceUnit} cadence within ${MAX_RECURRENCE_CANDIDATES} candidates What it means
Thrown by advanceToOnCadence when scanning up to MAX_RECURRENCE_CANDIDATES (10,000) cron fires yields none that land on the every-N-periods cadence. This is a safety valve preventing an unbounded loop on pathological schedules where the cron expression fires so frequently relative to the recurrence stride that no candidate ever satisfies the isOnCadence check.
Source
Thrown at packages/@n8n/scheduler/src/core/recurrence/kinds/recurring-cron.ts:94
* @param timezone IANA zone the period count runs in.
* @returns The next kept fire.
* @throws {InvalidScheduleError} When no fire lands on the cadence within the
* scan cap (a pathological schedule), instead of looping unbounded.
*/
function advanceToOnCadence(
cursor: CronCursor,
schedule: RecurringCronSchedule,
previousFire: Date,
timezone: string,
): Date {
for (let scanned = 0; scanned < MAX_RECURRENCE_CANDIDATES; scanned++) {
const candidate = cursor.next().toDate();
if (isOnCadence(previousFire, candidate, schedule, timezone)) {
return candidate;
}
}
throw new InvalidScheduleError(
`No fire of cron expression ${JSON.stringify(schedule.cronExpression)} lands on the every-${schedule.recurrenceSize}-${schedule.recurrenceUnit} cadence within ${MAX_RECURRENCE_CANDIDATES} candidates`,
);
}
/**
* The next fire of an "every N periods" cron schedule, strictly after `after`.
*
* The cron expression proposes candidate times; this returns the first one far
* enough from the previous fire: either within the same period (so a rule like
* "Monday and Wednesday" still fires both) or at least N periods later.
*
* @param schedule The cron expression plus its "every N periods" rule.
* @param after The previous fire; the period count is measured from here.
* @returns The next fire time.
*/
export function recurringCronNextRun(schedule: RecurringCronSchedule, after: Date): Date {
const timezone = resolvedTimezone(schedule);
const cursor = parseCron(schedule.cronExpression, after, timezone);View on GitHub (pinned to 5ac6606e81)
Solutions
- Align the cron expression to the recurrence unit: for 'every 2 weeks on Monday' use cron '0 9 * * 1' with recurrenceUnit='weeks', recurrenceSize=2.
- Avoid minute- or hour-level cron expressions when the recurrence unit is days, weeks, or months.
- If you need frequent fires with a long recurrence, reconsider whether recurring_cron is the right kind — a plain cron or interval may be more appropriate.
- Test the schedule with a dry-run or calculation tool to verify candidates land on the cadence within the cap.
Example fix
// before — every-minute cron with monthly stride never aligns
const schedule = {
kind: 'recurring_cron',
cronExpression: '* * * * *',
recurrenceUnit: 'months',
recurrenceSize: 2,
};
// after — monthly cron for every 2 months
const schedule = {
kind: 'recurring_cron',
cronExpression: '0 9 1 * *', // 9 AM on the 1st
recurrenceUnit: 'months',
recurrenceSize: 2,
}; Defensive patterns
Strategy: validation
Validate before calling
// Before registering, dry-run the schedule to check it produces fires
import { recurringCronNextRun } from '@n8n/scheduler';
try {
const now = new Date();
const next = recurringCronNextRun(schedule, now);
console.log('First fire:', next);
} catch (e) {
if (e instanceof InvalidScheduleError) {
console.error('Schedule is pathological — cron too frequent for recurrence unit');
}
} Try / catch
try {
const nextRun = recurringCronNextRun(schedule, after);
} catch (e) {
if (e instanceof InvalidScheduleError) {
// alert operator: the cron expression and recurrence unit are incompatible
logger.error('Schedule misconfiguration detected', {
cron: schedule.cronExpression,
unit: schedule.recurrenceUnit,
size: schedule.recurrenceSize,
});
// fall back to a plain cron or disable the job
}
} Prevention
- Align cron expression granularity with the recurrence unit — don't use minute-level cron with month-level recurrence.
- Run a dry-run calculation when saving a recurring_cron schedule to verify it produces fires.
- Document that recurring_cron expects the cron to fire at roughly the recurrence cadence.
When it happens
Trigger: A recurring_cron schedule where the cron expression fires far more often than the recurrence unit can accommodate. For example, a cron of '* * * * *' (every minute) with recurrenceUnit='months' and recurrenceSize=2 would generate thousands of minute-level candidates, none of which would align to a 2-month boundary relative to the previous fire. Also triggered by malformed cron expressions that somehow pass validateCron but produce candidates that never satisfy the cadence logic.
Common situations: Using an overly broad cron expression (e.g. every minute or every hour) with a coarse recurrence unit like 'months'. Misunderstanding that recurring_cron expects the cron to fire roughly at or near the desired cadence. Copy-pasting a cron expression from a different schedule type without adjusting for the recurrence rule.
Related errors
- recurring_cron.recurrenceUnit must be one of ${RecurringCron
- recurring_cron.recurrenceSize must be an integer of at least
- jitterRatio must be at least 0 and below 1, got ${lifecycleO
- ${key} must be a positive number of seconds, got ${value}
- concurrencyMode must be 'sequential' or 'concurrent', got ${
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/35aea5cb642c489d.
Report an issue: GitHub.