Windows: the statusline process itself (dist/index.js) never exits, 125-164 orphans and GBs of RSS (not #703)

Author: devsuitupCreated Sep 2, 2026Updated Sep 2, 2026

Summary

On Windows, the statusline process itself (node dist/index.js) accumulates: it does not exit after rendering. Over hours this reaches hundreds of processes and several GB of RSS. Two separate incidents measured on the same machine, one week apart.

This is not #703. That issue (fixed in 0.7.0) is about orphaned git.exe children. What accumulates here is the plugin's own main entry point, one per invocation.

Environment

  • claude-hud 0.8.0 (latest release), installed as a marketplace plugin
  • Windows 11 Pro 26200, 15.7 GB RAM
  • Several Claude Code sessions running in parallel on the machine
  • statusLine.refreshInterval: 5 in ~/.claude/settings.json

Measurements

2026-09-01 — 172 node processes, of which 164 were claude-hud. Available commit memory fell to 12 MB out of 40 GB, and a Claude Code session was killed with no trace, taking five in-flight subagents with it.

2026-09-02125 orphaned claude-hud processes for 4.26 GB. Every one had a dead parent. Targeted cleanup (matching the command line and a dead parent, killed PID by PID) brought the machine from 156 node processes / 5.8 GB down to 9 / 279 MB.

Identification is unambiguous: ~/.claude/settings.json builds the command literally as exec node "<plugin_dir>dist/index.js", so a process whose command line ends in dist/index.js is the statusline entry point. The Windows git worker would show windows-git-worker.js instead.

Suspected mechanism

Reading src/git-runner.ts and src/windows-git-worker.ts, one path stands out. On Windows each git call goes through a dedicated worker process spawned by WindowsGitRunner (src/git-runner.ts:82-156).

Two things combine there:

  1. The worker's ChildProcess is never .unref()ed. In Node, an un-unref'd child keeps the parent's event loop alive until the 'exit' event is observed — not until the application code has stopped waiting for it.
  2. WindowsGitRunner.close() (src/git-runner.ts:140-156) calls this.worker.kill() and resolves its promise after at most 2s, without ever verifying that the kill actually took effect.

So if the worker fails to exit — under the extreme memory pressure this bug itself creates, or with an AV/EDR holding the process — the parent dist/index.js stays alive waiting for an 'exit' that never arrives. That matches the observation that the survivors are dist/index.js and not windows-git-worker.js.

I want to be clear that this second part is read from the source, not proven by a controlled reproduction. The accumulation itself is measured; the reason the process stays alive is a hypothesis.

refreshInterval: 5 is an amplifier rather than a cause: the statusline is relaunched every 5 seconds per session, so with several sessions a small per-invocation failure rate is enough to reach the numbers above within hours.

Suggested directions

  • .unref() the worker ChildProcess so a stuck worker cannot pin the parent's event loop.
  • After kill(), wait for and verify the exit rather than resolving on a timer — a kill that is not verified is not a kill.
  • Consider a hard self-exit in the entry point: the statusline is a short-lived render, and outliving it by minutes is never correct.

Workaround in use

Raised refreshInterval from 5 to 30, which cuts invocations sixfold. gitStatus.enabled: false should remove the worker entirely, at the cost of git info in the HUD — I have not measured whether that alone stops the accumulation.