{"slug": "claude-might-be-saturating-your-machine", "title": "Claude might be saturating your machine", "summary": "A developer discovered that a Claude Code session left behind ten orphaned zsh processes that had been saturating all CPU cores for nearly two days. The processes were remnants of a load-testing script that failed to clean up because job control was disabled in the non-interactive shell. The developer shared a prompt to help others identify similar orphaned processes by checking load averages, parent PIDs, and full argument lists.", "body_md": "My laptop was sitting idle with the fan at full tilt. Nothing was running that I knew of. The culprit turned out to be ten orphaned busy-loops left behind by a Claude Code session two days earlier.\n\nIf your fan is roaring right now and you would rather not read the rest, paste this into a Claude Code session:\n\nMy machine seems idle but the fan is at full speed. Check the load average against my core count, then list the top CPU consumers with their PPID, elapsed time, and full argument list. I am looking for orphaned processes: anything reparented to PID 1 that has been burning CPU for hours or days, especially shells and interpreters where the process name alone tells you nothing. Show me what you find and what each one actually is before killing anything, and let me confirm the list first.\n\nThe \"show me before killing\" part is the important bit, and it is why the prompt is written that way rather than as \"find and kill whatever is using CPU.\" A long-running process pinned to PID 1 is a strong hint that something was abandoned, but it is not proof. Daemons legitimately live there, and your own `nohup`\n\n'd job or a detached build will look identical from a distance. Read the argument list before you agree to anything. In my case the arguments made it obvious in about two seconds.\n\nStart with the load average. This is a 10-core machine:\n\n``` bash\n$ uptime\n19:39  up 6 days,  6:14, 10 users, load averages: 122.91 167.84 162.08\n```\n\nLoad 122 on 10 cores is not idle. Next, the top CPU consumers:\n\n``` bash\n$ ps -Ao pcpu,pid,ppid,user,comm -r | head -12\n %CPU   PID  PPID USER   COMM\n139.8  8320     1 user   /Applications/Google Chrome.app/Contents/MacOS/Google Chrome\n 60.9 94281     1 user   /bin/zsh\n 59.4 94279     1 user   /bin/zsh\n 59.4 94288     1 user   /bin/zsh\n 59.1 94287     1 user   /bin/zsh\n 57.9 94285     1 user   /bin/zsh\n 57.6 94284     1 user   /bin/zsh\n 57.6 94286     1 user   /bin/zsh\n 57.3 94283     1 user   /bin/zsh\n 55.9 94282     1 user   /bin/zsh\n 51.1 94280     1 user   /bin/zsh\n```\n\nTen `zsh`\n\nprocesses at roughly 60% each, and every one has `PPID 1`\n\n. That is the tell: their parent died and `launchd`\n\nadopted them. `comm`\n\nonly gives you the binary name, so pull the full argument list to see what they actually are:\n\n``` bash\n$ ps -o pid,lstart,etime,pcpu,args -p 94279,94280,94281\n```\n\nNote the comma-separated list. Passing `-p`\n\nalongside `-A`\n\nsilently gets you every process on the box instead.\n\nThe arguments explained everything:\n\n```\n/bin/zsh -c source ~/.claude/shell-snapshots/snapshot-zsh-XXXX.sh 2>/dev/null || true && eval '\nSP=/private/tmp/claude-501/<project>/<session-id>/scratchpad\n# saturate all cores, then run the suite under contention\nNCPU=$(sysctl -n hw.ncpu)\nfor i in $(seq 1 $NCPU); do (while :; do :; done) & done\nLOADPIDS=$(jobs -p)\npnpm test:integration > \"$SP/load.log\" 2>&1\nkill $LOADPIDS 2>/dev/null\n...'\n```\n\nElapsed time was `01-22:41:17`\n\n. Just under two days of `while :; do :; done`\n\non every core.\n\nA previous session had deliberately pegged all ten cores to run an integration suite under CPU contention, then meant to clean up after itself. The test run itself was long gone, confirmed by the absence of any `vitest`\n\nor `pnpm`\n\nprocess:\n\n``` bash\n$ ps -Ao pid,ppid,pcpu,etime,comm | grep -Ei \"[n]ode|[v]itest|[p]npm\"\n58938 58933   0.0    00:59 .../bin/node\n88656 88648   0.0 02-00:57:58 .../bin/node\n```\n\nOnly the spinners were left. They accounted for about 700% of the 850% total CPU in use:\n\n``` bash\n$ ps -Ao pid,ppid,pcpu,comm | awk 'NR>1 && $3>20 {sum+=$3; n++} END {print \"procs >20% CPU:\", n, \" total %CPU:\", sum}'\nprocs >20% CPU: 12  total %CPU: 850.3\n```\n\nTwo things went wrong, and either alone would have been enough.\n\n`LOADPIDS=$(jobs -p)`\n\nwas the first. Job control is off in a non-interactive shell, so `jobs -p`\n\nreturned nothing and the cleanup `kill`\n\nhad no arguments to act on. Collect `$!`\n\nafter each background spawn instead:\n\n```\nLOADPIDS=\"\"\nfor i in $(seq 1 $NCPU); do\n  (while :; do :; done) &\n  LOADPIDS=\"$LOADPIDS $!\"\ndone\n```\n\nThe second is that the parent shell died before reaching the `kill`\n\nline at all. A cleanup step on the happy path is not cleanup. Use a trap so it fires even on interrupt:\n\n```\ntrap 'kill $LOADPIDS 2>/dev/null' EXIT INT TERM\n```\n\nPlain `kill`\n\nwas enough. No `-9`\n\nneeded:\n\n``` bash\n$ kill 94279 94280 94281 94282 94283 94284 94285 94286 94287 94288\n$ ps -o pid= -p 94279,94280,94281,94282,94283,94284,94285,94286,94287,94288 | wc -l\n0\n```\n\nAfterwards, Chrome was back on top where it belongs and nothing else broke 30%:\n\n``` bash\n$ ps -Ao pcpu,pid,comm -r | head -4\n %CPU   PID  COMM\n129.6  8320  /Applications/Google Chrome.app/Contents/MacOS/Google Chrome\n 43.2 58249  .../Google Chrome Helper (Renderer)\n 27.5 69725  .../UsageTrackingAgent\n```\n\nThe load average still read 74 at that point, which is expected. It is a decaying rolling average and lags reality by design. The 1-minute figure had already dropped from 122 to 74 and kept falling; the 15-minute figure was still carrying the previous quarter-hour of saturation. Judge the fix by the process list, not the load average. The fan takes another minute or two beyond the CPU drop while the heat already in the chassis dissipates.\n\n```\nuptime                              # load average well above your core count?\nsysctl -n hw.ncpu                   # what your core count actually is\nps -Ao pcpu,pid,ppid,user,comm -r | head -15\n```\n\nLook for processes eating CPU with `PPID 1`\n\nand a long `etime`\n\n. Reparenting to `launchd`\n\nplus an elapsed time measured in days means nobody is supervising it and nobody is going to clean it up. Shells and interpreters are worth a closer look, since `comm`\n\nshows you `/bin/zsh`\n\nor `node`\n\nand tells you nothing about what is inside. Get the arguments:\n\n```\nps -o pid,ppid,lstart,etime,pcpu,args -p <pid>\n```\n\nOn macOS, Activity Monitor sorted by CPU shows the same thing, but the process name column has the same problem: ten identical `zsh`\n\nrows and no hint of what they are running.\n\nAgents run real commands, including ones that deliberately consume resources, and a crashed or cancelled session does not necessarily take its children with it. If you let an agent spawn background work, it is worth knowing how to spot the leftovers. `ps -Ao pcpu,pid,ppid,user,comm -r | head`\n\ncosts nothing and would have caught this two days earlier.", "url": "https://wpnews.pro/news/claude-might-be-saturating-your-machine", "canonical_source": "https://dev.to/sidhantpanda/claude-might-be-saturating-your-machine-3h07", "published_at": "2026-07-16 17:53:53+00:00", "updated_at": "2026-07-16 18:05:06.841909+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools"], "entities": ["Claude Code", "Anthropic"], "alternates": {"html": "https://wpnews.pro/news/claude-might-be-saturating-your-machine", "markdown": "https://wpnews.pro/news/claude-might-be-saturating-your-machine.md", "text": "https://wpnews.pro/news/claude-might-be-saturating-your-machine.txt", "jsonld": "https://wpnews.pro/news/claude-might-be-saturating-your-machine.jsonld"}}