Skip to content

[Windows] Process hangs indefinitely when spawned as child subprocess via Volta shim (stdin EOF not propagated through cmd.exe → bash → volta → node chain) #420

@FelixIsaac

Description

@FelixIsaac

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

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.sh documentation 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions