n8n-io/n8n · error · InvalidScheduleError
No fire of cron expression
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.
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.recurrenceSize must be an integer of at…
- recurring_cron.recurrenceUnit must be one of
- concurrencyMode must be 'sequential' or 'concurrent', got
- jitterRatio must be at least 0 and below 1, got
- must be a positive number of seconds, got
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/35aea5cb642c489d.
Report an issue: GitHub.
Appendix: 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)