claude.exe ugrep subprocess wedges holding 6-9 GB RAM; parent does not SIGKILL on timeout
claude.exe ugrep subprocess wedges holding 6-9 GB RAM for 6-10 minutes; parent does not SIGKILL on timeout
Summary
Spawning claude.exe ugrep subprocesses to search across ~/.claude/projects/ (transcript / tool-result storage) can wedge for 6-10 minutes while holding 3-9 GB RAM each. Parent claude does not kill the wedged child on timeout — the child continues running as an orphan until it eventually self-exits (likely on SIGPIPE when parent closes stdout fd). On a 15 GB RAM workstation this causes near-system-wide unresponsiveness via swap thrashing.
Observed 3 separate wedge events in a single 1-hour session.
Environment
- OS: Ubuntu 24.04 / Linux 6.17.0-23-generic
- RAM: 15 GiB total, 31 GiB swap
- Claude Code: installed via
npmat/home/long/.nvm/versions/node/v24.14.1/lib/node_modules/@anthropic-ai/claude-code/ - Multiple long-running Claude Code sessions active concurrently (4 sessions, mix of foreground + background)
Evidence — process snapshots
Three independent wedge events captured in the same session:
PID RSS ETIME CMD (truncated)
484327 3531 MB 10:07 claude.exe ugrep -G --ignore-files --hidden -I \
--exclude-dir=.git --exclude-dir=.svn --exclude-dir=.hg \
--exclude-dir=.bzr --exclude-dir=.jj --exclude-dir=.sl \
-o '.{0,200}mcp-needs-auth-cache.{0,200}' \
/home/long/.claude/projects/-home-long-long/<uuid>/tool-results/b7vd6bwb8.txt
485087 2604 MB 6:42 claude.exe ugrep ... -o '.{0,80}claudeai.{0,200}' \
/home/long/.claude/projects/-home-long-long/<uuid>/tool-results/b7vd6bwb8.txt
497789 9297 MB 1:06+ claude.exe ugrep ... (same shape, later in session)
The target file b7vd6bwb8.txt is only 824 KB (verified via ls -lah). Total ~/.claude/projects/ directory is 1.7 GB. RSS of 3.5-9.3 GB cannot be explained by the target file alone.
Hypotheses
Two non-exclusive root causes:
1. Wrapper walks parent directory despite single-file argv. The --exclude-dir=.git --exclude-dir=.svn ... flags only make sense in recursive mode, but the command ends with a single file path. If the wrapper defaults to recursive mode when -G + --exclude-dir are set, it would walk all of ~/.claude/projects/ (1.7 GB) plus emit thousands of matches with the .{0,200}KEYWORD.{0,200} pattern.
2. Regex catastrophic backtracking. The non-anchored variable-length pattern .{0,200}KEYWORD.{0,200} with -o (print only match) on text-heavy input causes pcre2 to attempt many length combinations per position. For text where KEYWORD appears multiple times in close proximity, state machine memory + match-output buffer can grow into GB range.
3. No SIGKILL on parent timeout. Whatever timeout claude applies to the subprocess, it appears to be a soft stop (close stdout fd, stop reading) rather than a hard kill. The child remains running until SIGPIPE on the next write — which only fires when the OS finally tears down the pipe, often after the process has done substantial additional work.
Impact
- Each wedge holds 3-9 GB RAM for 6-10 minutes.
- On a system with multiple Claude Code sessions, wedges compound and push the box into swap thrashing (available RAM < 500 MB).
- Swap thrashing slows every UI interaction by 10-100x (page-in latency on every memory access).
- After two such events, system available RAM was 270 MB / 15 GB; user reported "computer slow and nearly crash".
- Killing the wedged PID with
kill -9immediately reclaims the full RSS (3-9 GB), confirming the memory is not leaked — it's pinned to a running-but-stuck process.
Suggested fixes
- Hard kill on subprocess timeout — when the timeout fires, send SIGKILL (or at minimum SIGTERM then SIGKILL after grace period) to the subprocess instead of only closing stdout. This is the highest-impact fix.
- Bound the search scope explicitly — when
claude.exe ugrepis invoked with a single-file argument, ignore directory-recursion flags (--exclude-dir,--ignore-files). Audit the wrapper's argv parsing to ensure single-file mode never walks a directory.
- Cap regex worst-case cost — for the
-ooutput mode, set a hard limit on match-output buffer size and abort with a clear error if exceeded. pcre2 supports aMATCH_LIMITparameter that bounds backtracking explicitly.
- Expose a
--max-runtimeor RSS cap as a defensive default — e.g., abort after 30 seconds or 1 GB RSS, whichever first.
Repro suggestion (for triage)
Set up a Claude Code workspace with several MB+ of accumulated session transcripts in ~/.claude/projects/. Trigger a tool that causes the agent to search across transcripts using a broad regex with variable-length context (.{0,N}KEYWORD.{0,N}). Observe with ps -eo pid,rss,etime,comm | grep claude.exe during execution.
A simulated trigger that reliably reproduces this would be valuable — happy to help isolate one if it would speed up triage.
Workaround (community)
Until fixed, users on RAM-constrained workstations need an external watchdog:
- systemd-user timer scanning for
claude.exe ugrepprocesses withetime > 60s - Desktop notification on detection, manual confirmation before kill (auto-kill risks killing legitimate long-running work in edge cases)
- Interactive TUI to inspect + kill individual wedges
This is non-ideal — the fix belongs in the Claude Code subprocess management layer.
---
Diagnostic file: Process snapshots, system memory state, and the exact argv of each wedged invocation are reproducible from the user's local environment. Will provide on request.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗