#12448: fix: prevent cron list/status from silently skipping due jobs
stale
Cluster:
Cron Job Management Fixes
## Summary
Fixes #12440
When `cron list` or `cron status` is called after a job's scheduled time but before the 60-second timer fires, `recomputeNextRuns()` advances `nextRunAtMs` to the next cycle — effectively skipping the due run without executing it.
**Root cause:** `recomputeNextRuns()` treats past-due jobs (`now >= nextRunAtMs`) the same as jobs with missing `nextRunAtMs` — it recomputes both. For `start()` this is correct because `runMissedJobs()` runs first, but `list()` and `status()` never execute missed jobs before calling `recomputeNextRuns()`.
**Fix:** Add a `preserveDue` option to `recomputeNextRuns()` that skips past-due jobs so the timer can still pick them up. `list()` and `status()` pass `{ preserveDue: true }` while `start()`, `onTimer()`, and `add()` retain the default behaviour.
### Changed files
- `src/cron/service/jobs.ts` — `recomputeNextRuns()` accepts optional `{ preserveDue: true }` to leave past-due `nextRunAtMs` untouched
- `src/cron/service/ops.ts` — `status()` and `list()` pass `{ preserveDue: true }`
- `src/cron/service/jobs.test.ts` — regression tests covering the new behaviour
## Test plan
- [x] New unit tests: `recomputeNextRuns` with `preserveDue: true` leaves past-due jobs untouched
- [x] New unit tests: `recomputeNextRuns` without `preserveDue` advances past-due jobs (existing behaviour)
- [x] New unit tests: `preserveDue: true` still fills in missing `nextRunAtMs`
- [x] Full test suite passes (975/975, 1 pre-existing failure unrelated to cron)
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR changes cron’s `recomputeNextRuns()` to accept an optional `preserveDue` flag. When set, past-due jobs (`now >= nextRunAtMs`) are left untouched rather than being recomputed forward, preventing `cron list` / `cron status` from accidentally “skipping” a due run before the timer fires. `status()` and `list()` now call `recomputeNextRuns(state, { preserveDue: true })`, while the scheduler paths (`start`, timer, add) keep the default behavior.
A new unit test file (`src/cron/service/jobs.test.ts`) was added to cover both behaviors (default recompute advances past-due jobs; `preserveDue` keeps them due; `preserveDue` still fills missing `nextRunAtMs`).
<h3>Confidence Score: 3/5</h3>
- Behavioral change looks correct, but the added tests likely won’t compile as written without completing CronServiceState deps stubs.
- The core logic change is small and targeted (only gates recomputation when due + preserveDue), and the call sites are appropriate (list/status). The main merge-blocker risk is build/test failure from the new test helper constructing an incomplete CronServiceState (and secondarily, using a fixed /tmp path that could become an FS write if a codepath changes).
- src/cron/service/jobs.test.ts
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#14667: fix: preserve missed cron runs when updating job schedule
by WalterSumbon · 2026-02-12
89.3%
#12443: fix(cron): don't advance past-due jobs that haven't been executed
by rummangeminicode · 2026-02-09
89.2%
#12982: fix(cron): prevent status/list from advancing overdue job nextRunAtMs
by hclsys · 2026-02-10
87.6%
#11108: fix(cron): prevent missed jobs from being skipped on timer recompute
by Bentlybro · 2026-02-07
87.1%
#9060: Fix: Preserve scheduled cron jobs after gateway restart
by vishaltandale00 · 2026-02-04
86.3%
#10918: fix(cron): add tolerance for timer precision and skip due jobs in r...
by Cherwayway · 2026-02-07
86.0%
#13796: fix: skip recomputing nextRunAtMs for running cron jobs (#13739)
by echoVic · 2026-02-11
84.6%
#12303: fix(cron): correct nextRunAtMs calculation and prevent timer stall
by colddonkey · 2026-02-09
84.4%
#8379: fix(cron): handle past-due one-shot 'at' jobs that haven't run yet
by Gerrald12312 · 2026-02-04
83.6%
#9393: fix(cron): avoid recomputeNextRuns on forceReload
by matthewpapa07 · 2026-02-05
83.6%