You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
High CPU usage observed with multiple concurrent Claude Code sessions #397
Killing the processes and removing the ccstatusline extension resolved the slowdown.
Possible hypothesis:
The Stop hook fires npx -y ccstatusline@latest per session per response. With multiple concurrent sessions, this may multiply the number of spawned node processes linearly. However, I was unable to fully confirm this as the root cause — other factors (VS Code TypeScript server, Expo bundler) were also running at the time.
Reacted by Todor Andonov, Kartikay Sharma, Tony Wu, Williams Wang, wwp, RobTuckfield and Peter Elmered
Lovely app, but indeed I reproduce this issue, and this is quite annoying (several claude terminals open here on Rocky Linux 9).
@wwp Is your install using npx/bunx or did you pin the version? If not, try pinning the version (uninstall / reinstall and you'll see the pinning option) and see if that helps.
@sirmalloc npx install here, as the pin install cannot complete here since it attempts (because of -g) to write to /usr/local, my user is not granted to. BTW the pin install silently fails, it's when I tried the command manually that I found what the error was.
I changed my npm prefix to be in my HOME and unisntalled/reinstalled ccstatusline, pin install. that does not change anything as I regularly see node <HOME>/.npm-global/bin/ccstatusline processes (several at the same time, sometimes) that eat cpu.
Is that because of widgets that trigger transcripts parsing? I always run several claude code terminal sessions, and potentially quite big transcripts. I reduced the refresh interval from 10 to 30, but still, doing nothing makes the cpu have constant noticeable peaks.
+1 — same observation on macOS with several concurrent Claude Code sessions. I profiled where the CPU actually goes; verified against upstream/main @ 19c6665.
Setup
macOS (Darwin 25.6.0), statusline with the default widget set, multiple Claude Code sessions running concurrently. Harness: a 13 MB transcript rendered repeatedly (4 workers × 20 renders), measuring CPU time of the statusline process.
Where the CPU goes per repaint
Process-ancestor walk. On macOS the walk from the statusline process up to the owning TTY spawns ps twice per ancestor process (once for the parent pid, once for the TTY). With shells nested a few levels deep (worktrees, tmux, IDE terminals) that is a lot of short-lived processes on every repaint.
Eager proxy-agent loading.https-proxy-agent — and the whole transitive undici module graph — is loaded on every render even when no proxy is configured. Profiling showed ~40 ms of each render spent just compiling that module graph.
Measured effect of the two fixes
one ps -o ppid= -o tty= -p <pid> per ancestor instead of two spawns;
load the proxy agent lazily, only when a proxy is actually configured:
CPU 27.2 → 20.6 CPU-s over the whole harness, i.e. 0.34 → 0.26 CPU-s per render (−24%); wall clock 7.5 s → 5.6 s.
Both are implemented in #728 with tests. If the maintainer prefers them as two separate PRs, happy to split.
This is serious, especially for people who use any kind of multi agent workflow. I personally use herdr. It was a disaster. Following is a write up of what I got (I use Windows and WSL), written by Opus.
I do understand that the README and the installation process both tell us the cost of having npx -y ccstatusline@latest in claude's settings. HOWEVER, IT SHOULDN'T EVEN BE AN ACCEPTABLE OPTION TO BEGIN WITH, let alone advised as the README's only installation example.
Incident: the status line slowed every process start
Summary
The Claude Code status line ran npx -y ccstatusline@latest every 10 seconds in every open Claude session. Once the machine was busy, each run took longer than 10 seconds, so new runs started before old ones finished and piled up. That made every process start slow for every session. It showed up only as a plugin hook timing out. Changing the command fixed it in about 20 minutes.
Impact
The pile-up: 240 processes running npx or ccstatusline (183 Git Bash, 37 Node), using 4.3 GB of memory. 86 of them had lost their parent process. The oldest was under a minute old, so this was constant churn, not a leak.
Process starts: a bare Node start took 6.2 s from Git Bash, the Node binary itself 11.6 s, and mise --version 9.8 s. From PowerShell, cmd /c exit took 1.3 s. Normal is tens of milliseconds.
Who it hit: every session, coordinators and workers alike. Every Bash tool call, hook and build step starts processes. Busiest by CPU after vmmemWSL: bash 20%, node 15%, plus Defender scanning each new process.
Visible symptom: the claude-mem-cowork prompt hook timed out at 8 s (it took 8.5–15 s), and its output was thrown away. That plugin is unpaired and does nothing, so losing its output cost nothing.
Cause
An expensive command: each refresh started Git Bash, then mise's npx script, which asked the npm registry what @latest is, then Node. That's several process starts and a network round trip, every 10 seconds.
Multiplied: the same command ran in every session (herdr listed 14 agents). A cost that was invisible with one or two sessions became large with a fleet of workers.
A feedback loop: slower starts made each refresh longer than its 10 seconds, so refreshes overlapped. More processes made starts slower still. The orphans suggest that when a refresh was abandoned, its child processes kept running (an inference, not verified).
On a busy machine, process creation is slow enough to start the loop.
Detection
By chance: you kept seeing the timeout message. scripts/machine status and the watch alert on memory and CPU load.
Resolution
01:38: installed ccstatusline globally (2.2.32), switched the status line to the installed copy, and set the refresh to 30 s.
01:40: the pile-up was gone: 11 such processes, no orphans. A Node start took 79 ms, the hook 273 ms, cmd /c exit 111 ms. Total processes fell from 905 to 611.
Lessons
A command every session runs on a timer costs sessions × frequency. Check that cost before adding one, and again whenever the worker fleet grows.
npx -y pkg@latest belongs in one-off commands, never on a timer: every run means a registry lookup and a fresh install check.
Some measurements that may help here, from running about 30 to 60 concurrent sessions with long transcripts on macOS.
What dominates per-render cost:renderMultipleLines runs getTranscriptAnalysis whenever stdin carries transcript_path (src/ccstatusline.ts:112-122 at v2.2.32), and that always computes token metrics (src/utils/jsonl-metrics.ts:669-672), whatever widgets are configured. Nothing is kept between renders, so every repaint streams the whole transcript and runs JSON.parse on every line. The streamed read added in 2.2.28/2.2.29 fixed the memory blowup we hit on 2.2.17 (around 1 GB per render; 29 renders started in the same second used about 25 GB between them, and the machine reached 37 GB of swap), but CPU and I/O still grow with the file on every render.
Measured on Apple Silicon, with a realistic stdin payload and a 466 MB real transcript, median of 5 runs with /usr/bin/time -l:
Peak RSS
Wall time
v2.2.32, full payload
281 MB
0.88 s
v2.2.17, transcript_path stripped from stdin
107 MB
0.16 s
Two possible fixes, smallest first:
Only analyze the transcript when a configured widget needs it. Many configs use only stdin data (model, context, rate_limits), and for them the whole parse is wasted.
Read incrementally. Cache the device, inode, byte offset and running totals per transcript (somewhere like ~/.cache/ccstatusline/), parse only the bytes appended since the last render, and rebuild when the inode changes or the file shrinks.
For reference, I wrote a Rust port that does option 2 while keeping byte-identical output with v2.2.32 (checked by a harness over 3,103 cases). On the same transcript it measures 2.6 MB and about 3 ms per warm render, and 60 concurrent renders take 157 MB in total. The approach is in https://github.com/danielnc/ccstatusline-rs if any of it is useful here; notes from reading the code, including a few related issues (#485, #571, #740), are in its docs/upstream-review.md.
I had to uninstall ccstatusline because it was eating up 20% CPU across 6 Claude Code Sessions on MacOS. Opus's investigation:
Each of your 6 open Claude sessions ran ccstatusline every 10 seconds. Each run started node, which then started about 11 more processes: ps to find the terminal, stty for the width, security to read the keychain, and git. macOS checks the signature of every new process, so syspolicyd had to check about 90 of them per second. When I ran it by hand I counted 11 children per run, but the live sessions produced more than that. I did not find out why before removing it.
Activity
wwp commentedon Jun 12, 2026
Lovely app, but indeed I reproduce this issue, and this is quite annoying (several claude terminals open here on Rocky Linux 9).
sirmalloc commentedon Jun 12, 2026
@wwp Is your install using npx/bunx or did you pin the version? If not, try pinning the version (uninstall / reinstall and you'll see the pinning option) and see if that helps.
wwp commentedon Jun 12, 2026
@sirmalloc npx install here, as the pin install cannot complete here since it attempts (because of -g) to write to /usr/local, my user is not granted to. BTW the pin install silently fails, it's when I tried the command manually that I found what the error was.
wwp commentedon Jul 8, 2026
I changed my npm prefix to be in my HOME and unisntalled/reinstalled ccstatusline, pin install. that does not change anything as I regularly see node
<HOME>/.npm-global/bin/ccstatuslineprocesses (several at the same time, sometimes) that eat cpu.Is that because of widgets that trigger transcripts parsing? I always run several claude code terminal sessions, and potentially quite big transcripts. I reduced the refresh interval from 10 to 30, but still, doing nothing makes the cpu have constant noticeable peaks.
perf: reduce per-repaint CPU under concurrent sessions (#2)
Merge PR #2 final revision (BSD-safe ps, walk parity) into fork main
bench(render): N-session baseline harness for the daemon epic (#14)
bench(render): N-session baseline harness for the daemon epic (#14) (#26
Merge upstream v2.2.32 into fork main
axisrow commentedon Oct 13, 2026
+1 — same observation on macOS with several concurrent Claude Code sessions. I profiled where the CPU actually goes; verified against upstream/main @ 19c6665.
Setup
macOS (Darwin 25.6.0), statusline with the default widget set, multiple Claude Code sessions running concurrently. Harness: a 13 MB transcript rendered repeatedly (4 workers × 20 renders), measuring CPU time of the statusline process.
Where the CPU goes per repaint
pstwice per ancestor process (once for the parent pid, once for the TTY). With shells nested a few levels deep (worktrees, tmux, IDE terminals) that is a lot of short-lived processes on every repaint.https-proxy-agent— and the whole transitive undici module graph — is loaded on every render even when no proxy is configured. Profiling showed ~40 ms of each render spent just compiling that module graph.Measured effect of the two fixes
ps -o ppid= -o tty= -p <pid>per ancestor instead of two spawns;CPU 27.2 → 20.6 CPU-s over the whole harness, i.e. 0.34 → 0.26 CPU-s per render (−24%); wall clock 7.5 s → 5.6 s.
Both are implemented in #728 with tests. If the maintainer prefers them as two separate PRs, happy to split.
seankao31 commentedon Oct 17, 2026
This is serious, especially for people who use any kind of multi agent workflow. I personally use herdr. It was a disaster. Following is a write up of what I got (I use Windows and WSL), written by Opus.
I do understand that the README and the installation process both tell us the cost of having
npx -y ccstatusline@latestin claude's settings. HOWEVER, IT SHOULDN'T EVEN BE AN ACCEPTABLE OPTION TO BEGIN WITH, let alone advised as the README's only installation example.Incident: the status line slowed every process start
Summary
The Claude Code status line ran
npx -y ccstatusline@latestevery 10 seconds in every open Claude session. Once the machine was busy, each run took longer than 10 seconds, so new runs started before old ones finished and piled up. That made every process start slow for every session. It showed up only as a plugin hook timing out. Changing the command fixed it in about 20 minutes.Impact
Cause
Detection
By chance: you kept seeing the timeout message. scripts/machine status and the watch alert on memory and CPU load.
Resolution
Lessons
danielnc commentedon Oct 23, 2026
Some measurements that may help here, from running about 30 to 60 concurrent sessions with long transcripts on macOS.
What dominates per-render cost:
renderMultipleLinesrunsgetTranscriptAnalysiswhenever stdin carriestranscript_path(src/ccstatusline.ts:112-122at v2.2.32), and that always computes token metrics (src/utils/jsonl-metrics.ts:669-672), whatever widgets are configured. Nothing is kept between renders, so every repaint streams the whole transcript and runsJSON.parseon every line. The streamed read added in 2.2.28/2.2.29 fixed the memory blowup we hit on 2.2.17 (around 1 GB per render; 29 renders started in the same second used about 25 GB between them, and the machine reached 37 GB of swap), but CPU and I/O still grow with the file on every render.Measured on Apple Silicon, with a realistic stdin payload and a 466 MB real transcript, median of 5 runs with
/usr/bin/time -l:transcript_pathstripped from stdinTwo possible fixes, smallest first:
rate_limits), and for them the whole parse is wasted.~/.cache/ccstatusline/), parse only the bytes appended since the last render, and rebuild when the inode changes or the file shrinks.For reference, I wrote a Rust port that does option 2 while keeping byte-identical output with v2.2.32 (checked by a harness over 3,103 cases). On the same transcript it measures 2.6 MB and about 3 ms per warm render, and 60 concurrent renders take 157 MB in total. The approach is in https://github.com/danielnc/ccstatusline-rs if any of it is useful here; notes from reading the code, including a few related issues (#485, #571, #740), are in its
docs/upstream-review.md.Thanks for ccstatusline, it's a great tool.
kaibyao commentedon Nov 4, 2026
I had to uninstall ccstatusline because it was eating up 20% CPU across 6 Claude Code Sessions on MacOS. Opus's investigation: