Skip to content

Cron scheduler skips runs due to forceReload + recomputeNextRuns on timer tick #10771

@TechnicalParadox

Description

@TechnicalParadox

Summary

Cron jobs with schedule.kind=cron can skip scheduled runs after 2026.2.3. On each timer tick, CronService forces a reload and recomputes nextRunAtMs before checking due jobs. computeNextRunAtMs() uses Croner.nextRun(now), which returns the next run after now. If the timer fires at the scheduled boundary, nextRunAtMs is advanced to the next interval and runDueJobs() sees nothing due.

Observed

  • Hourly/30m jobs skip intervals; nextRunAtMs jumps ahead even though the run never executed.
  • This started after 2026.2.3 (Mac, LaunchAgent).

Root Cause (code path)

onTimer() in src/cron/service/timer.ts:

await ensureLoaded(state, { forceReload: true });
await runDueJobs(state);

ensureLoaded() calls recomputeNextRuns(), which calls computeNextRunAtMs() with nowMs. For cron schedules it does:

new Cron(expr, { timezone }).nextRun(new Date(nowMs))

Croner nextRun() returns the next occurrence after now, so a run exactly at now is skipped.

Minimal Fix (works locally)

Avoid recomputing before the due check. Example:

- await ensureLoaded(state, { forceReload: true });
+ await ensureLoaded(state);

Safer Alternative Fixes

  • Only force-reload/recompute when the store file mtime changes.
  • Or, in computeNextRunAtMs(), if Cron(expr).match(now) then return now before calling nextRun().
  • Or, move recomputeNextRuns() to after runDueJobs() on each tick.

Notes on side effects

Removing the forced reload means out-of-band edits to the cron store file won’t be picked up until restart or an API call. If that’s why forceReload exists, a conditional reload (mtime check) would preserve that behavior without skipping runs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions