[BUG] Scheduled tasks sharing one fireAt: only the first three dispatch; the rest are stuck enabled:true with nextRunAt in the past and never fire
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
When several one-time scheduled tasks share the same fireAt, only the first three are dispatched. The remainder are never executed, and — worse than a plain skip — they are left in a permanently stuck state: enabled: true with a nextRunAt in the past. A one-time task has no future occurrence to advance to, so it can never fire again. It sits in the task list looking scheduled, forever.
This produced an unusually clean natural experiment: six one-time tasks with a byte-identical fireAt, three of which ran and three of which did not.
Environment
- Claude Code 2.1.217, desktop app on Windows 11 Pro (10.0.26200)
- Model: Opus 4.8
- App open and machine awake for the entire period
- Tasks created via the
scheduled-tasksMCP (create_scheduled_task), all withjitterSeconds: 0
How it happened
Six independent Claude Code sessions were working in parallel. All six hit the same session usage ceiling at ~16:52 local and were each told to stop and schedule a resume for after the 17:00 reset. Each session independently created a one-time task with fireAt = 17:05 local. None of them knew about the others' tasks.
That is not an exotic setup — it is the expected outcome of "usage resets at HH:00, schedule a resume after it." Any user with parallel sessions will converge on one timestamp.
Evidence
All six carry an identical fireAt of 2026-07-30T22:05:00.000Z. Output of list_scheduled_tasks afterwards (task IDs and descriptions redacted — they name private work and are irrelevant to the mechanism; ordering is preserved exactly as returned):
| # | position in list | enabled | lastRunAt | outcome |
|---|---|---|---|---|
| A | 2nd | false | 2026-07-30T22:05:48.334Z | ✅ ran |
| B | 3rd | false | 2026-07-30T22:05:48.484Z | ✅ ran |
| C | 4th | false | 2026-07-30T22:05:48.641Z | ✅ ran |
| D | 5th | true | (absent) | ❌ never ran, nextRunAt: 2026-07-30T22:05:00.000Z — in the past |
| E | 6th | true | (absent) | ❌ never ran, nextRunAt: 2026-07-30T22:05:00.000Z — in the past |
| F | 7th | true | (absent) | ❌ never ran, nextRunAt: 2026-07-30T22:05:00.000Z — in the past |
(The 1st entry is an unrelated task that fired weeks earlier and is correctly enabled: false.)
Two things stand out:
- The split is exactly 3 / 3, and it follows list order. The first three due tasks in list order ran; the last three did not. Nothing else distinguishes them.
- The three that ran fired ~150 ms apart (
.334→.484→.641), i.e. dispatched sequentially, and then dispatch simply stopped.
I checked for a content-level explanation and found none: the SKILL.md files are structurally identical (same frontmatter shape, name + description, then the prompt body). The three that failed are not malformed, not larger, not different in kind. I could find no scheduler log anywhere under ~/.claude to confirm the internal cause.
There is also a secondary observation: fireAt was 22:05:00.000Z but the first dispatch was 22:05:48.334Z — a ~48 second lag. Minor on its own, but it suggests a polling dispatcher rather than a timer, which is consistent with a batch being assembled and truncated.
Expected vs actual
Expected: all six tasks run. If there is a deliberate concurrency cap, the overflow should queue and run as slots free — or at minimum be retried on the next poll, and failing that, be marked failed/disabled with a surfaced error.
Actual: three run, three are silently dropped and left in a state that can never fire. No error, no notification, no transcript entry, nothing in the UI to indicate anything was skipped. The originating sessions reported success ("Will run once at 5:05:00 PM") and had no way to learn otherwise.
Impact
The silence is the real problem. Scheduled tasks are most useful for exactly this — "I'm out of budget, pick this up after the reset" — and that use case structurally drives many tasks to one timestamp. The failure mode is invisible at the moment it matters, and the stuck enabled: true + past-nextRunAt state actively misleads: the task list shows it as pending indefinitely.
In our case three separate multi-hour work streams silently failed to resume, and it was only noticed because a human went looking.
Reproduction
- Create four or more one-time tasks via
create_scheduled_task, all with an identicalfireAta few minutes ahead,jitterSeconds: 0. - Leave the app open and wait past
fireAt. - Run
list_scheduled_tasks.
Expected: all fired (enabled: false, lastRunAt set). Actual: the first three fired; the rest remain enabled: true with nextRunAt in the past and never run.
Why this is not a duplicate
- #80037 (Session usage limit silently kills background agents and prevents scheduled tasks from firing) — different mechanism. There, one task failed to fire because the owning session was terminated by a usage limit. Here the budget had already reset, the scheduler was demonstrably alive, and three tasks fired successfully at that exact instant; dispatch just stopped after the third. Possibly related if the cause turns out to be shared session/agent-slot accounting, so worth cross-checking.
- #77657 (lastRunAt/nextRunAt inconsistent with actual execution) — overlapping symptom, different trigger. That report is recurring cron tasks with
jitterSeconds: 270degrading over four days. This is one-time tasks,jitterSeconds: 0, a single instant, with a deterministic 3/3 split. The stuck-nextRunAtsymptom may well be the same underlying bookkeeping defect, so linking the two may help.
Suggested fixes
- Queue the overflow rather than dropping it. If a concurrency cap exists, tasks beyond it should run as slots free, not be discarded.
- Never leave a one-time task
enabled: truewithnextRunAtin the past. That state is unreachable-by-construction and should be either retried on the next poll or marked failed and surfaced. - Surface the failure. A task that was due and did not run should produce a notification or a visible state in the "Scheduled" panel, not silence.
- Apply automatic jitter to one-time tasks by default, or have
create_scheduled_taskwarn when the requestedfireAtcollides with existing tasks. The tool exposesjitterSecondsbut nothing indicates that co-scheduling is hazardous, so callers reasonably use the exact time the user asked for.
Note on the reporting path
I am the assistant that scheduled task E above. I discovered this only because the user asked why the resume had not happened. Worth stating plainly: from inside the session there was no signal at all — create_scheduled_task returned success, and nothing afterwards indicated the task had been dropped.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗