← Back to PRs

#12303: fix(cron): correct nextRunAtMs calculation and prevent timer stall

by colddonkey open 2026-02-09 03:26 View on GitHub →
stale
## Summary Fixes the recurring cron jobs `nextRunAtMs` miscalculation that caused jobs to never fire on schedule. **Root causes identified and fixed:** 1. **`computeNextRunAtMs` boundary handling** (`schedule.ts`): The 1ms lookback (`nowMs - 1`) was insufficient because croner compares at second granularity. A call at exactly HH:00:00.000 would still land in the same second, causing croner to skip the current match and return the next occurrence (e.g., +24h for daily crons). Changed to a 1-second lookback with a fallback to strict `nextRun(nowMs)` when the lookback returns a past match. 2. **Timer death on `state.running` guard** (`timer.ts`): When `onTimer` was called while a previous execution was still running (triggered by external `armTimer` calls from add/update/remove operations), it returned early **without re-arming the timer**. This caused the cron scheduler to silently stall until the running job finished. If the running job took longer than expected, subsequent timer ticks would be lost entirely. Now re-arms with `MAX_TIMER_DELAY_MS` (60s) to prevent both hot-looping and permanent timer death. 3. **Added debug logging** at `onTimer` entry and at the `state.running` early-return path so silent stalls are detectable in logs. Fixes #12278. Related: #12263, #12276. ## Test plan - [x] 5 new regression tests for hourly, daily, and 30-min cron expressions with timezones - [x] Tests for exact-boundary and 1ms-after-boundary edge cases - [x] Updated timer re-arm test to verify safe-delay behavior - [x] Full cron test suite passes (93/93 tests across 20 files) <!-- greptile_comment --> <h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR updates the cron scheduler in two places: - `src/cron/schedule.ts` changes `computeNextRunAtMs` to use a 1-second lookback (instead of 1ms) when calling Croner’s `nextRun`, and falls back to a strict `nextRun(now)` if the lookback result would be in the past. This addresses exact-boundary scheduling where Croner compares at second granularity. - `src/cron/service/timer.ts` fixes a scheduler stall when `onTimer` is invoked while another `onTimer` execution is still running: the early-return path now re-arms the timer with a safe delay (`MAX_TIMER_DELAY_MS`, 60s) so the scheduler can recover. The PR also adds regression tests for boundary/timezone cron expressions and adjusts an existing timer overlap regression test to assert the new re-arm behavior. `.gitignore` is updated to ignore `.cursor/`. <h3>Confidence Score: 5/5</h3> - This PR is safe to merge with minimal risk. - Changes are localized to cron scheduling and timer re-arming, and are covered by targeted regression tests for the boundary/timezone cases and the timer stall scenario. No correctness issues were found in the updated logic paths during review of the modified files and their call sites. - No files require special attention <!-- greptile_other_comments_section --> <sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub> **Context used:** - Context from `dashboard` - CLAUDE.md ([source](https://app.greptile.com/review/custom-context?memory=fd949e91-5c3a-4ab5-90a1-cbe184fd6ce8)) - Context from `dashboard` - AGENTS.md ([source](https://app.greptile.com/review/custom-context?memory=0d0c8278-ef8e-4d6c-ab21-f5527e322f13)) <!-- /greptile_comment -->

Most Similar PRs