#3693: fix(cron): delete deleteAfterRun jobs regardless of execution status
Cluster:
Cron Job Enhancements
## Summary
- Fixed infinite loop bug where `deleteAfterRun: true` one-shot jobs would loop every 40ms when skipped or failed
- Changed deletion condition to trigger regardless of execution status (ok, skipped, or error)
- Added tests for skipped and failed scenarios
## Problem
One-shot jobs with `deleteAfterRun: true` were only deleted when `status === "ok"`. When the job was skipped or failed:
- The job was not deleted (`shouldDelete = false`)
- The job was not disabled (line 86-89 only handles `status === "ok"`)
- `nextRunAtMs` remained in the past
- Timer immediately re-fired → 40ms infinite loop
## Solution
Changed `src/cron/service/timer.ts:82-83` from:
```typescript
const shouldDelete =
job.schedule.kind === "at" && status === "ok" && job.deleteAfterRun === true;
```
To:
```typescript
const shouldDelete =
job.schedule.kind === "at" && job.deleteAfterRun === true;
```
This matches the intended semantics of `deleteAfterRun: true` — "delete after execution regardless of outcome".
## Test plan
- [x] Existing cron tests pass (54 tests)
- [x] Added test: `deleteAfterRun: true` + `status: "skipped"` → job deleted
- [x] Added test: `deleteAfterRun: true` + `status: "error"` → job deleted
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR fixes an infinite re-run loop for one-shot (`schedule.kind === "at"`) jobs configured with `deleteAfterRun: true` by deleting them after execution regardless of whether they finished `ok`, `skipped`, or `error` (`src/cron/service/timer.ts`). It also extends the CronService test suite with new cases that verify delete-after-run behavior for skipped main-lane jobs loaded from disk and for isolated jobs that return an error status.
<h3>Confidence Score: 4/5</h3>
- This PR looks safe to merge and addresses a real runaway-timer scenario.
- The production change is narrowly scoped (only affects deletion criteria for one-shot deleteAfterRun jobs) and is backed by new tests covering skipped and errored outcomes. The only concern is minor test robustness around stopping/cleanup ordering if CronService.stop ever becomes async.
- src/cron/service.runs-one-shot-main-job-disables-it.test.ts
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#18960: fix: don't disable one-shot cron jobs on skipped status
by jwchmodx · 2026-02-17
86.9%
#5428: fix(Cron): prevent one-shot loop on skip
by imshrishk · 2026-01-31
85.3%
#11657: fix(cron): treat skipped heartbeat as ok for one-shot jobs
by DukeDeSouth · 2026-02-08
85.2%
#12303: fix(cron): correct nextRunAtMs calculation and prevent timer stall
by colddonkey · 2026-02-09
83.0%
#11108: fix(cron): prevent missed jobs from being skipped on timer recompute
by Bentlybro · 2026-02-07
82.8%
#18144: fix(cron): clear stuck runningAtMs after timeout and add maintenanc...
by taw0002 · 2026-02-16
82.5%
#8825: fix: prevent cron infinite retry loop with exponential backoff
by dbottme · 2026-02-04
82.1%
#12443: fix(cron): don't advance past-due jobs that haven't been executed
by rummangeminicode · 2026-02-09
82.0%
#12982: fix(cron): prevent status/list from advancing overdue job nextRunAtMs
by hclsys · 2026-02-10
82.0%
#17064: fix(cron): prevent control-plane starvation during startup catch-up...
by donggyu9208 · 2026-02-15
81.7%