Repository navigation
Description
Summary
When ccstatusline is invoked as a child process from another Node.js script using spawn('ccstatusline', [], { shell: true }) on Windows with a Volta-managed installation, the process hangs indefinitely and never exits. Each invocation leaks one node.exe process. In a long Claude Code session this accumulated to 961 leaked processes → 2.1 GB RAM → 90.5% kernel-mode CPU from Windows scheduler overhead.
Root cause
The Volta-installed ccstatusline binary is a bash shim:
volta run "$(basename $0)" "$@"When spawned via spawn('ccstatusline', [], { shell: true }), the process chain on Windows is:
parent node → cmd.exe (shell:true) → bash (Volta shim) → volta → node (ccstatusline)
ccstatusline reads stdin with for await (const chunk of process.stdin) (line ~77342 in dist). The parent calls cc.stdin.end() to signal EOF — but on Windows, stdin EOF does not propagate through the cmd.exe → bash → volta chain. The inner for await loop never terminates. ccstatusline sits waiting forever. The parent also hangs waiting on cc.on('close', ...).
Claude Code's statusline lifecycle abandons (does not kill) a hung process when a new refresh triggers, so processes accumulate indefinitely.
This is a known Node.js/Windows issue
process.stdinnever returns with using Git Bash for Windows nodejs/node#5620 —process.stdinnever returns in Git Bash/Windows- Accessing process.stdin hangs on Windows nodejs/node#10836 — canonical Windows stdin hang
- Piping
process.stdintochild.stdinleaves behind an open handle nodejs/node#32291 — pipingprocess.stdintochild.stdinleaves open handle spawnwith stdio/stdin 'pipe' options causes pwsh shell to hang after execution nodejs/node#52364 —spawn+stdin:'pipe'causes pwsh to hang (still broken 2024)- Volta breaks with
child_process.execincmd(Windows) volta-cli/volta#1199 — Volta shims + child_process confirmed broken on Windows - [Bug] ccusage statusline causes infinite process spawning loop leading to 100% CPU usage ccusage/ccusage#459 — identical failure mode in the same Claude Code ecosystem (statusline subprocess never exits, 300%+ CPU, 3+ GB RAM)
The fix (2 lines in src/ccstatusline.ts)
After renderMultipleLines writes its output, instead of relying on event loop drain (which doesn't work through the Volta chain):
// After rendering is complete:
process.stdout.write(formattedOutput, () => {
process.stdin.destroy(); // unblock event loop; EOF won't arrive through Volta chain on Windows
process.exit(0); // guarantee exit
});Or more simply, wherever main() finishes rendering and before it returns:
process.stdin.destroy();process.stdin.destroy() is sufficient — it unregisters the stdin handle from the event loop so Node exits naturally without waiting for EOF that will never arrive.
Notes
- This does not affect the primary use case (
"command": "ccstatusline"directly in Claude Code settings) — in that case Claude Code pipes directly to ccstatusline with a single process layer and EOF propagates correctly. - The bug only manifests when ccstatusline is used as a pipe-through subprocess, which is a misuse pattern but one that
statusline-wrapper.shdocumentation may inadvertently encourage. - ccstatusline's Custom Command Widget already has a 1000ms kill timeout which correctly prevents this class of bug for child widgets — the same pattern (guaranteed exit) should be applied to the main process.
Environment
- Windows 11 Pro 10.0.26200
- ccstatusline v2.2.19
- Volta-managed Node installation
- Node.js v24.15.0
Suggested additional fix (documentation)
Add a note to docs/WINDOWS.md warning that spawning ccstatusline as a child_process subprocess with shell: true on Windows will hang due to Volta's bash shim. The correct usage is as the direct statusLine command.
Activity
mrns commentedon Jun 25, 2026
Reproduces without Volta — the root cause is more general than the Volta shim.
Setup: Windows 11, ccstatusline 2.2.22, Node v24.14.0, Claude Code with statusLine.command: "ccstatusline". No Volta anywhere — Claude Code invokes the command through its bundled Git Bash (sh.exe → node). Same accumulation described here: over a long session it built to 280+ stuck node/sh processes (~11 GB RAM, CPU pegged at 100%).
Minimal, shim-independent repro (no Volta, no Claude Code):
The trigger is simply stdin never reaching EOF promptly; any Windows process chain that delays EOF (Volta, Git Bash/MSYS, …) hits it.
So "configuring it directly as the command works correctly" isn't universal — it depends on how the host wires up stdin on Windows.
This supports #421's approach (stdin read timeout + explicit exit). +1 for merging.