Scheduled tasks: wake-time catch-up drops tasks (lastRunAt stamped without a session) and spawn-storms concurrent headless runs

Open 💬 0 comments Opened Jun 10, 2026 by MisterTuur

Environment

Claude Code desktop app on macOS 26.5.1 (CLI claude --version: 2.1.97). Local scheduled tasks (~/.claude/scheduled-tasks/): 5 recurring tasks (2 daily, 3 weekly Monday-morning, staggered cron slots ~10 min apart). The machine is typically asleep at the scheduled times, so tasks fire via wake-time catch-up.

Observed behaviors

Reconstructed from session transcripts (every scheduled run's transcript starts with a "type":"queue-operation" enqueue line carrying <scheduled-task name="…"> — scanning all transcript first-lines gives a reliable run ledger).

  1. Catch-up drop — lastRunAt stamped without a session. On 2026-06-09T14:15Z the scheduler stamped lastRunAt for three overdue tasks within ~300 ms; only one produced a session transcript. The other two never ran — silently. list_scheduled_tasks reports them as having run.
  2. Spawn storm with duplicate spawns. On 2026-06-02 ~06:20Z a wake catch-up spawned eight headless sessions at once, including duplicates of the same task (one daily task ×3, another ×4). The concurrent burst hit API rate limiting and one session died mid-run with API Error: Server is temporarily limiting requests (not your usage limit) · Rate limited — no retry, no resume, no surfaced failure. Its lastRunAt is indistinguishable from a successful run.
  3. No run-outcome state. lastRunAt is the only signal and it cannot distinguish success / mid-run death / never-spawned. In our case a weekly routine was silently dead for two weeks before anyone noticed.
  4. Bonus quirk: updating a task's cron expression (update_scheduled_task) to a slot that already passed today triggers an immediate catch-up run of that task.

Suggestions

  • Serialize or cap wake-time catch-up spawns (the rate-limit death is a direct consequence of the burst).
  • Dedupe catch-up to at most one spawn per task.
  • Retry/backoff on rate-limit errors in headless scheduled runs.
  • Record a per-run outcome (success / error / dropped), not just lastRunAt — and only stamp it when a session actually spawned.

Happy to provide redacted transcript excerpts.

View original on GitHub ↗