Schedule triggers self-heal after missed executions
Schedule triggers that got stuck after a missed execution window now recover automatically. The fix replaces a strict equality check with elapsed-time comparison, so workflows no longer require manual intervention after Redis failovers, restarts, or downtime.
Schedule triggers in n8n could silently stop working for up to a year after missing a single execution window. The root cause was a strict equality check in the recurrence logic: when lastExecution became stale (due to an instance restart, Redis failover, leadership change, or simple downtime), the trigger would never fire again.
The fix replaces that strict equality with an elapsed-time >= check across all four interval types: hours, days, weeks, and months. Now when enough time has passed since the last execution, the trigger fires regardless of whether intermediate windows were missed. The system self-heals without manual database intervention or workflow recreation.
This fix also resolves a related issue where changing a schedule's interval configuration could cause permanent blocking — the stale lastExecution from the old schedule no longer prevents the new schedule from running.
In the Schedule node's , the recurrence check now uses modulo arithmetic to calculate elapsed time, ensuring consistent behavior across hour (24), day (365), week (52), and month (12) intervals.
View Original GitHub Description
Summary
The recurrenceCheck function used strict equality (===) to determine if an interval-based Schedule Trigger should fire. If a single trigger window was missed (instance restart, leadership change, Redis failover, downtime), the lastExecution value in staticData became permanently stale and the check never passed again — silently blocking the workflow for up to 364 days.
This replaces the strict equality with an elapsed-time >= check:
// Before: only fires on the exact expected tick
dayOfYear === (intervalSize + lastExecution) % 365
// After: fires once enough time has elapsed (self-healing)
(dayOfYear - lastExecution + 365) % 365 >= intervalSize
Applied to all four interval types: hours, days, weeks, months. The weeks same-week re-trigger behavior is preserved.
This also fixes the schedule-change variant (NODE-4625, PR https://github.com/n8n-io/n8n/pull/25671) as a side effect — when a user changes the interval config, the stale lastExecution from the old schedule no longer causes a permanent block.
How to test
- Create a workflow with Schedule Trigger set to "Every 2 hours"
- Activate and let it fire once
- Deactivate, then set
recurrenceRulesinstaticDatato a stale value via DB - Reactivate — the workflow should self-heal and fire on the next cron tick
Related Linear tickets, Github issues, and Community forum posts
- https://linear.app/n8n/issue/NODE-4831
- https://linear.app/n8n/issue/NODE-4625
- fixes https://github.com/n8n-io/n8n/issues/23711
- Closes https://github.com/n8n-io/n8n/pull/25671
- Fixes https://github.com/n8n-io/n8n/issues/23711
Review / Merge checklist
- I have seen this code, I have run this code, and I take responsibility for this code.
- PR title and summary are descriptive. (conventions)
- Docs updated or follow-up ticket created.
- Tests included.
🤖 PR Summary generated by AI