Scheduled-task CLI process does not exit after unattended run completes

Status Open
Maintainer reply None cached
Activity 5 comments · opened Sep 2, 2026

Summary

A scheduled task's underlying claude CLI process does not terminate after the unattended run finishes its turn. Over ~24 hours, this produced 24 separate leaked claude processes on one machine, each still consuming 2-11% CPU hours after their run should have completed - enough concurrent load to push a local Nuxt/Vite dev-server boot from ~1-2 minutes to over 10 minutes (628s Nitro build observed), causing unrelated test-suite timeouts.

Environment

  • macOS (Darwin 23.2.0), Claude Code desktop app, claude-code version 2.1.255
  • Task type: a CronCreate-style scheduled task (SKILL.md-based), cron expression 23 * * * 1-5 (hourly, weekdays)
  • The task's own script logic is a short synchronous Node script that calls process.exit(0) on its one early-exit path and otherwise falls off the end of the file with no open handles/timers - ruled out as the cause of the hang (see below).

Evidence

ps -eo pid,etime,pcpu,command showed 24 distinct claude CLI processes (command line matching claude --output-format stream-json ... --allowedTools ..., no --resume=<uuid> flag - i.e. each is a fresh, non-resumed session, consistent with a scheduled task rather than an interactive/resumed one), spaced almost exactly 60 minutes apart:

 PID     ETIME     %CPU
16348   23:53:07   11.5
50410   23:27:38    3.6
70106   23:10:21    7.0
43689   22:09:52    6.9
25737   21:10:07    2.6
 7879   20:09:55    7.6
89352   19:10:06    6.4
71643   18:10:06    7.6
53612   17:10:06    4.9
35791   16:10:06    7.2
17998   15:10:06    7.3
99521   14:10:06    4.3
81679   13:10:06    3.1
64152   12:10:06    5.2
46082   11:10:06    6.3
28273   10:10:06    2.5
10472   09:10:05    8.7
91930   08:10:05    7.5
73918   07:10:05    7.3
54536   06:10:02    6.4
29659   05:10:00    6.6
 6895   04:10:01    7.5
83886   03:10:01    5.2
60839   02:10:01    6.7
36734   01:10:01    6.7

The corresponding session records (via the CCD session-management MCP tools) showed each of these runs as isRunning: false - i.e. the app itself believed the task had completed - yet the OS process was still alive and actively burning CPU at the time of inspection, for as long as ~24 hours after its run should have finished.

Only the most recent firing (10 minutes old at time of inspection) was legitimately still running.

What I ruled out

  • Not the task's own script: the scheduled task runs a short Node script via a single synchronous execFileSync call per step; it calls process.exit(0) on its --clear branch and otherwise has no async work, timers, or open sockets that would keep Node's event loop alive.
  • Not an interactive/resumed session: none of the 24 stuck processes carried a --resume=<uuid> flag; two other long-running processes on the same machine that did carry --resume= (one of them the conversation used to file this report) behaved correctly and were unaffected.
  • Not a stale/wrong-server issue: this reproduced across ~24 independent firings of the same task over a full day.

Impact

With N scheduled tasks configured at similar frequency, leaked processes accumulate without bound (24 in ~24 hours here) since they never get cleaned up on their own. Each is a live Node/Claude process, so this degrades local machine performance for any concurrent work (a local dev-server build/boot went from ~1-2 minutes to 628 seconds under this load), and there's no user-facing signal that this is happening - the session/task UI reports isRunning: false for these while the OS process is still very much alive.

Repro

  1. Create a scheduled task (SKILL.md-based, via the scheduled-tasks mechanism) that fires frequently (e.g. hourly).
  2. Let it fire several times unattended.
  3. After a few hours, check ps -eo pid,etime,pcpu,command | grep claude - observe multiple long-lived claude CLI processes with no --resume= flag, one per firing, none of them exited, despite the app reporting each run as completed/not-running.

Suggested area to investigate

Whatever code path handles process teardown after an unattended/scheduled-task session reports its final turn and "completes" - it appears the process is not being reliably killed/reaped even though the session record is correctly marked not-running.

---
Reported via Claude Code session assistance.

View original on GitHub ↗

This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗