Scheduled tasks fail silently when the app is closed; no signal distinguishing "running on schedule" from "only running while app is open"
Compiled from ~2 weeks of running 3-4 autonomous scheduled tasks continuously for a personal trading-bot project. Four distinct, real failure modes, all sharing one shape: a background process failing with zero surfaced signal to the user, caught only because a human happened to check state manually or because a separately-built watchdog caught it.
Incident 1 — scheduled task execution gaps, no alert
- 8/24-8/25: a scheduled task (running a local Python script) didn't execute for ~20 hours overnight. No error, no alert — caught only because a separately-built watchdog script flagged "no completed cycle in 1198 minutes."
- 8/24: a different scheduled task (an interactive-agent trading bot) stopped firing on its 5-minute cadence and needed two manual kicks that day to resume. No error surfaced; discovered by the user checking in.
- Both tasks were configured correctly (
enabled: true, correct cron expression, confirmed vialist_scheduled_tasks) — the schedule itself wasn't wrong, execution just silently stopped happening.
Incident 2 — long-lived session resume breaks across app runtime updates
- A continuously-running interactive session went completely silent after the desktop app's runtime updated twice in succession (2.1.227 → 2.1.229 → 2.1.234). The session's own state (written to disk every cycle) was intact, but the session never resumed and never alerted that it had stopped. Required manually starting a new session with a hand-written recovery prompt.
- The same runtime update changed the CLI binary itself: it moved from a macOS-native binary to a Linux x86-64 ELF executable (
claude-code-vm/2.1.229/claude) — not runnable directly on macOS. This means there's no externally-subprocess-callableclaude -p ...CLI on the machine anymore; scheduled agent sessions now run inside an internal VM with no external trigger hook. Any workaround relying on shelling out toclaudeto force-trigger a session is now permanently broken, independent of the resume bug.
Incident 3 (root-caused, most severe) — silent permission failure, 4 days
- A launchd-scheduled Python script (run via
/usr/bin/python3, no interactive parent) was silently blocked by macOS TCC from reading anything under~/Documents— every read from 2026-08-14 19:08 UTC onward failed withPermissionError. - This failure only ever produced a log line, never a user-facing alert. Discovered only when the user happened to ask about a specific trade whose staleness didn't add up.
- Root cause confirmed and fixed by granting the headless Python binary Full Disk Access manually — but Full Disk Access's own file picker didn't surface
/usr/bin/python3in its list (had to be typed via Cmd+Shift+G), and the binary never triggered a permission-prompt dialog the way a GUI app normally would.
Incident 4 — root cause confirmed: scheduled tasks require the app to be open, undocumented in practice
- 2026-08-27: traced Incident 1's exact gap pattern to a root cause by correlating a scanning script's own execution log against how long a Claude Code session had been continuously open. Real gaps of 20-120 minutes occurred on three separate days during market hours; zero gaps occurred on the one day a session stayed open continuously through the entire market-hours window.
create_scheduled_task's own tool description does state "Scheduled tasks run while this app is open. If the app is closed when a task is due, it runs on next launch" — so this is technically documented, but the practical implication for a recurring task on a tight cadence (e.g. every 5 minutes over a multi-hour window) is not obvious from that line: it silently produces unpredictable, unbackfilled gaps rather than a late-but-complete catch-up. There's no in-product signal distinguishing "task is running on schedule" from "task only runs when the app happens to be open."- The same mechanism also explains a task's
lastRunAtsometimes landing hours outside its own configured cron window — a delayed catch-up run, easily misread as a scheduler malfunction. - Contrast with a working example: a separately-built watchdog script for the same project (Python + launchd, not a Claude Code scheduled task) ran independently of the app's open/closed state and correctly alerted on all three gap days via a native OS notification — suggesting the underlying constraint isn't inherent to background automation on the machine, only to the Claude Code scheduled-task mechanism specifically.
Common thread across all four
None of these produced any prompt, notification, or visible failure state to the user at the time they happened. Every one was caught late, by a human noticing something didn't add up, or by a second system independently built to watch the first. For a background-automation product, a background process failing silently — whether from not firing, not resuming, or losing OS-level permissions — seems like the single highest-value class of bug to close, since by construction the user has no way to know it's happening until they go looking.
Suggested asks
- Any user-facing signal (even a passive one, like a visible "N missed runs" indicator) when a scheduled task's actual execution cadence diverges from its configured cadence.
- Some notification when a long-lived session fails to resume after an app update, rather than the session just going quiet.
- A documented, external-facing way to trigger a Claude Code session programmatically post-2.1.229, now that the old subprocess-CLI approach is confirmed dead.
- For sub-hourly recurring tasks specifically: either make the "app must be open" constraint visible in the scheduled-tasks UI (e.g. show actual last-N-runs against the configured cadence so a gap is visible at a glance), or clarify in the scheduling UI itself that these are best-effort-while-open rather than cron-equivalent, so a user relying on one for a real-money trading bot isn't discovering the distinction only after the fact.