[BUG] Scheduler catch-up storm on restart: re-fires daily tasks and runs enabled:false tasks (ghost fires, lastRunAt not updated)

Open 💬 1 comment Opened Jul 4, 2026 by Palo-Alto-AI-Research-Lab

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)

  1. 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).
  2. Disabled task fired: a task with enabled: false since 3 days prior (cron 20 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. Its lastRunAt still shows the pre-disable date.
  3. Wrong-day fire: a weekly task (cron 45 5 * * 1, Monday) fired on Thursday.

Expected

  • enabled: false is 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.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗