Repository navigation
Description
Summary
In 2.2.22, the cached git command runner calls execFileSync("git", …) without a timeout, while every other external CLI call in the same bundle passes timeout: CLI_TIMEOUT (5000 ms). A git invocation that blocks therefore hangs the statusline process indefinitely, with nothing to reap it.
This is a separate hang path from the stdin issue in #485 / #420, which #421 addresses. Fixing the stdin read will not close this one.
Evidence
From the installed dist/ccstatusline.js (2.2.22), all 12 synchronous exec call sites, extracted with brace matching:
With timeout: CLI_TIMEOUT (6):
execFileSync("git", args, { …, cwd, timeout: CLI_TIMEOUT, windowsHide: true })
execFileSync("ssh", ["-G", host], { …, timeout: CLI_TIMEOUT, … })
execFileSync(cli, ["--version"], { …, timeout: CLI_TIMEOUT, … })
execFileSync(cli, ["auth", "status", "--hostname", host], { …, timeout: CLI_TIMEOUT, … })
execFileSync("gh", args, { …, cwd, timeout: CLI_TIMEOUT, … })
execFileSync("glab", args, { …, cwd, timeout: CLI_TIMEOUT, … })
Without (6):
execFileSync("git", args, { <-- the cached git runner
encoding: "utf8",
stdio: ["pipe", "pipe", "ignore"],
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
windowsHide: true,
...cwd2 ? { cwd: cwd2 } : {}
})
execSync("tput cols 2>/dev/null", { … })
execSync(`ps -o ppid= -p ${pid}`, { …, shell: "/bin/sh" })
execSync(`ps -o tty= -p ${pid}`, { …, shell: "/bin/sh" })
execSync(`${cmd} 2>/dev/null | awk '{print $2}'`, { …, shell: "/bin/sh" })
spawnSync(command, args, { stdio: "ignore", windowsHide: true })
CLI_TIMEOUT = 5000.
The last five are local, fast, and mostly POSIX-only. The first is the interesting one: it sits immediately after the memory/persistent cache lookup in the git command helper, so it is the path every git widget takes on a cache miss.
Why it bites
On this machine the working repository lives on a Google Drive mount, where git is routinely slow and occasionally blocks. With the default gitCacheTtlSeconds: 5 and a refreshInterval of 10, the cache expires before every render, so this call runs on essentially every redraw. Multiply by several concurrent Claude Code sessions and any non-trivial hang rate accumulates.
Observed on this machine: 60 orphaned ccstatusline node processes, 4,511 MB resident, oldest 143.7 hours, every one with a dead parent. That population is presumably a mix of this path and the stdin path in #485 — I cannot attribute individual processes to one or the other after the fact, and I want to be careful not to claim more than I measured.
Suggested fix
Add timeout: CLI_TIMEOUT to the cached git runner, matching its sibling. One line, and it bounds the worst case to 5 s rather than forever.
Separately, and lower priority: when a sync exec does time out, Node throws but leaves nothing to clean up — there is no .kill( anywhere in the bundle except process.kill(process.pid, sig) inside the vendored signal-exit, which re-raises a signal on the current process rather than terminating a child. Worth a look if timed-out children are observed surviving.
Related
- Processes pile up when Claude Code keeps stdin open #485 — processes pile up when Claude Code keeps stdin open
- [Windows] Process hangs indefinitely when spawned as child subprocess via Volta shim (stdin EOF not propagated through cmd.exe → bash → volta → node chain) #420 — [Windows] process hangs indefinitely, stdin EOF not propagated
- fix: prevent process hang on Windows when stdin EOF does not propagate through Volta shim #421 — fix: prevent process hang when stdin EOF does not propagate
Those three concern the stdin read. This issue is about the exec path and is independent of them.
Environment
- ccstatusline 2.2.22 (global npm install)
- Windows 11, Claude Code, several concurrent sessions
- Repository on a Google Drive (
G:) mount
Activity
fix: add timeout to the cached git runner
fix: add timeout to the cached git runner (sirmalloc#585)
fix: add timeout to the cached git runner