Skip to content

fix: prevent process hang on Windows when stdin EOF does not propagate through Volta shim#421

Open
FelixIsaac wants to merge 1 commit into
sirmalloc:mainfrom
FelixIsaac:fix/windows-volta-stdin-hang
Open

fix: prevent process hang on Windows when stdin EOF does not propagate through Volta shim#421
FelixIsaac wants to merge 1 commit into
sirmalloc:mainfrom
FelixIsaac:fix/windows-volta-stdin-hang

Conversation

@FelixIsaac
Copy link
Copy Markdown

Problem

On Windows with a Volta-managed ccstatusline, spawning ccstatusline as a child subprocess causes it to hang indefinitely. The process chain:

parent node → cmd.exe (shell:true) → bash (Volta shim) → volta → node (ccstatusline)

The for await (const chunk of process.stdin) loop never terminates because stdin EOF from the parent does not propagate through cmd.exe → bash → volta on Windows. Claude Code's statusline lifecycle abandons (does not kill) processes that take too long — so each refresh leaks one hung node.exe process.

Real-world impact: 961 leaked node.exe processes, 2.1 GB RAM, 90.5% kernel-mode CPU from Windows scheduler overhead managing ~1000 sleeping processes.

This is a known Windows/Node.js behavior:

Note: the normal use case ("command": "ccstatusline" direct from Claude Code) is not affected — Claude Code pipes directly, single process layer, EOF works correctly.

Fix

Fix 1 — readStdin(): Replace for await with event-based reading + 3s bail timeout. When EOF never arrives (Volta chain), the timeout destroys stdin and resolves the Promise. Data already accumulated in chunks[] is preserved and returned normally — no data loss.

Fix 2 — main(): Add explicit process.exit(0) after renderMultipleLines. Claude Code's "abandon not kill" lifecycle means any residual async handles from rendering keep the process alive indefinitely. An explicit exit prevents accumulation.

Build note

Bun was not available in this environment so dist/ was not rebuilt. The source change is in src/ccstatusline.ts only — maintainer build required before publishing.

…e through Volta shim

On Windows with a Volta-managed ccstatusline, spawning ccstatusline as a child
subprocess causes it to hang indefinitely. The process chain is:

  parent node -> cmd.exe (shell:true) -> bash (Volta shim) -> volta -> node

The for-await stdin loop never receives EOF because stdin EOF from the parent
does not propagate through cmd.exe -> bash -> volta on Windows. Each Claude Code
statusline refresh spawns a new hung process; in one real session this accumulated
to 961 leaked node.exe processes, 2.1 GB RAM, 90.5% kernel-mode CPU.

Fix 1 (readStdin): replace for-await with event-based reading + 3s bail timeout.
When EOF never arrives, the timeout destroys stdin and resolves the promise.
Data already accumulated in chunks[] is returned normally (no data loss).

Fix 2 (main): add explicit process.exit(0) after renderMultipleLines. Claude Code
abandons (does not kill) statusline processes that run long; an explicit exit
prevents any residual async handles from keeping the event loop alive.

Related: nodejs/node#32291, volta-cli/volta#1199, ryoppippi/ccusage#459
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant