{"record":{"id":"35aea5cb642c489d","repo":"n8n-io/n8n","slug":"no-fire-of-cron-expression-json-stringify-schedu","errorCode":null,"errorMessage":"No fire of cron expression ${JSON.stringify(schedule.cronExpression)} lands on the every-${schedule.recurrenceSize}-${schedule.recurrenceUnit} cadence within ${MAX_RECURRENCE_CANDIDATES} candidates","messagePattern":"No fire of cron expression (.+?) lands on the every-(.+?)-(.+?) cadence within (.+?) candidates","errorType":"validation","errorClass":"InvalidScheduleError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/scheduler/src/core/recurrence/kinds/recurring-cron.ts","lineNumber":94,"sourceCode":" * @param timezone IANA zone the period count runs in.\n * @returns The next kept fire.\n * @throws {InvalidScheduleError} When no fire lands on the cadence within the\n * scan cap (a pathological schedule), instead of looping unbounded.\n */\nfunction advanceToOnCadence(\n\tcursor: CronCursor,\n\tschedule: RecurringCronSchedule,\n\tpreviousFire: Date,\n\ttimezone: string,\n): Date {\n\tfor (let scanned = 0; scanned < MAX_RECURRENCE_CANDIDATES; scanned++) {\n\t\tconst candidate = cursor.next().toDate();\n\t\tif (isOnCadence(previousFire, candidate, schedule, timezone)) {\n\t\t\treturn candidate;\n\t\t}\n\t}\n\n\tthrow new InvalidScheduleError(\n\t\t`No fire of cron expression ${JSON.stringify(schedule.cronExpression)} lands on the every-${schedule.recurrenceSize}-${schedule.recurrenceUnit} cadence within ${MAX_RECURRENCE_CANDIDATES} candidates`,\n\t);\n}\n\n/**\n * The next fire of an \"every N periods\" cron schedule, strictly after `after`.\n *\n * The cron expression proposes candidate times; this returns the first one far\n * enough from the previous fire: either within the same period (so a rule like\n * \"Monday and Wednesday\" still fires both) or at least N periods later.\n *\n * @param schedule The cron expression plus its \"every N periods\" rule.\n * @param after The previous fire; the period count is measured from here.\n * @returns The next fire time.\n */\nexport function recurringCronNextRun(schedule: RecurringCronSchedule, after: Date): Date {\n\tconst timezone = resolvedTimezone(schedule);\n\tconst cursor = parseCron(schedule.cronExpression, after, timezone);","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/scheduler/src/core/recurrence/kinds/recurring-cron.ts#L76-L112","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before — every-minute cron with monthly stride never aligns\nconst schedule = {\n  kind: 'recurring_cron',\n  cronExpression: '* * * * *',\n  recurrenceUnit: 'months',\n  recurrenceSize: 2,\n};\n// after — monthly cron for every 2 months\nconst schedule = {\n  kind: 'recurring_cron',\n  cronExpression: '0 9 1 * *', // 9 AM on the 1st\n  recurrenceUnit: 'months',\n  recurrenceSize: 2,\n};","handlingStrategy":"validation","validationCode":"// Before registering, dry-run the schedule to check it produces fires\nimport { recurringCronNextRun } from '@n8n/scheduler';\n\ntry {\n  const now = new Date();\n  const next = recurringCronNextRun(schedule, now);\n  console.log('First fire:', next);\n} catch (e) {\n  if (e instanceof InvalidScheduleError) {\n    console.error('Schedule is pathological — cron too frequent for recurrence unit');\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  const nextRun = recurringCronNextRun(schedule, after);\n} catch (e) {\n  if (e instanceof InvalidScheduleError) {\n    // alert operator: the cron expression and recurrence unit are incompatible\n    logger.error('Schedule misconfiguration detected', {\n      cron: schedule.cronExpression,\n      unit: schedule.recurrenceUnit,\n      size: schedule.recurrenceSize,\n    });\n    // fall back to a plain cron or disable the job\n  }\n}","preventionTips":["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."],"tags":["scheduler","recurrence","loop-safety","configuration"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}