[BUG] Scheduler catch-up storm on restart: re-fires daily tasks and runs enabled:false tasks (ghost fires, lastRunAt not updated)
Summary
The scheduled-tasks runner performs a "catch-up storm" after an app restart: it re-fires daily tasks that already ran that day, and fires tasks that are explicitly enabled: false (some disabled days earlier). lastRunAt in list_scheduled_tasks is NOT updated by these ghost runs, so the registry claims the task never ran while a full claude -p session (with its full context cold-load) was actually spawned and billed against the subscription bucket.
Environment
- Claude Code 2.1.185 (observed; just updated to 2.1.201, unknown if fixed)
- Windows 11 Pro 10.0.26200, desktop app + scheduled-tasks MCP
Observed on 2026-07-03 (all times local, verified from ~/.claude/projects/**/*.jsonl session logs)
- Restart storm ~23:33: six daily tasks each fired a SECOND time within minutes of each other, far from their cron slots (crons: 00:20, 00:50, 04:00, 05:10, 05:30, 05:40). The duplicate runs burned ~30M tokens (one nightly maintenance task alone: 24.8M across its 2 runs).
- Disabled task fired: a task with
enabled: falsesince 3 days prior (cron20 5 * * *) fired at 20:29 — wrong time AND disabled. Session log shows the standard "This is an automated run of a scheduled task" preamble, so it came from the scheduler, not a manual run. ItslastRunAtstill shows the pre-disable date. - Wrong-day fire: a weekly task (cron
45 5 * * 1, Monday) fired on Thursday.
Expected
enabled: falseis authoritative: a disabled task never spawns a session.- A restart never re-fires a task that already ran in the current cron period (missed-run catch-up should be opt-in, or at least skip already-ran-today tasks).
- Every real run updates
lastRunAt, so ghost runs are at least visible.
Impact
Each ghost fire is a full claude -p cold start (~0.7-2M tokens with a large user CLAUDE.md + MCP schemas), so a single restart storm can silently burn tens of millions of tokens per day. Users tracking down "which background service eats my tokens" will find the scheduler itself.
Workaround found
Moving the task folder out of ~/.claude/scheduled-tasks/ (archive) prevents ghost fires (the runner then has no SKILL.md to execute); enabled: false alone does not. Related older request: #59981 (delete_scheduled_task API) — a real delete would also solve this.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗