Bundled ugrep shadowing `grep` can exhaust system memory — froze a 16 GB machine twice
Summary
Claude Code's Bash tool shadows the shell's grep with a ugrep applet embedded in the Claude Code binary. On ordinary text-analysis patterns that applet allocates ~600× more memory than GNU grep, with no bound. On this machine it twice consumed all RAM and all swap and required a hard power cycle, losing in-flight work.
The underlying matcher bug is upstream (reported separately to Genivia/ugrep). The issue here is the substitution itself: replacing a universal tool with one that has a radically different memory profile, invoked unattended, with no cap and no fallback.
Versions
- Claude Code
2.1.233and2.1.234(both affected) - Bundled applet reports:
ugrep 7.5.0 x86_64-pc-linux-gnu +sse2 - TUXEDO OS 24.04 (Ubuntu 24.04 base), kernel 6.17.0, 16 GB RAM, 12 GB swap
The mechanism
~/.claude/shell-snapshots/snapshot-bash-*.sh defines:
function grep {
local _cc_bin="${CLAUDE_CODE_EXECPATH:-}"
[[ -x $_cc_bin ]] || _cc_bin=/home/user/.local/bin/claude
...
(exec -a ugrep "$_cc_bin" -G --ignore-files --hidden -I --exclude-dir=.git ... "$@")
}
Because the binary is exec'd by its resolved versioned path, these processes appear in ps, OOM dumps, and cgroup accounting as 2.1.233 / 2.1.234 rather than as anything grep-like. That made diagnosis considerably harder — the OOM killer reported an unrecognisable process name, and it took a full investigation to establish that "the thing eating 20 GB" was Claude Code's own grep.
Impact observed
Two hard freezes requiring a power cycle:
- 2026-08-17 18:23 — four kernel OOM kills over three hours; all 16 GB RAM and all 12 GB swap consumed;
Free swap = 0kBfrom the first kill onward; page cache reduced to ~1 MB, so every process includingsystemdwas faulting its own text off disk. Input handling fell 38 seconds behind. 36 of 79 in-flight subagent results lost. - 2026-08-18 10:38 — same signature, two applet processes at 8.9 GB and 14.1 GB.
At the moment of the first freeze, the two applet processes held 20.05 GiB — 78.3% of all anonymous memory. For scale, the four interactive Claude Code sessions themselves held 1.76 GiB combined (6.9%), and Firefox 2.49 GiB. The sessions were not the problem; a single grep was.
With a userspace OOM killer since installed, we logged 16 further runaways in 11 minutes of ordinary work, peaking at 6.8 GB each. They are frequent, not exotic.
Reproducer
python3 -c "
import random; random.seed(1)
w='the quick brown fox jumps over a lazy dog while pondering matters of some importance'.split()
out=[]
for i in range(1200):
s=' '.join(random.choice(w) for _ in range(random.randint(15,30)))
if i%40==0: s='It follows that '+s
out.append(s+'.')
open('repro.txt','w').write(' '.join(out)+'\n')
"
# inside a Claude Code Bash tool call, or with the shadow function sourced:
grep -o -h '[^.]\{0,90\}It follows that[^.]\{0,90\}\.' repro.txt # ~790 MB
command grep -o -h '[^.]\{0,90\}It follows that[^.]\{0,90\}\.' repro.txt # ~4.6 MB
Peak RSS on the same 150 KB file, by bound:
| bound n | bundled ugrep | GNU grep |
|---:|---:|---:|
| 20 | 12.5 MB | 2.9 MB |
| 40 | 26.7 MB | 3.1 MB |
| 60 | 282.7 MB | 3.6 MB |
| 90 | 788.2 MB | 4.6 MB |
On real corpus files the same pattern exceeded 2.5 GB, and in production 6.8 GB.
Note for anyone verifying: bash -c "grep ..." does not inherit the shadow function, so it silently tests /usr/bin/grep and shows nothing. Invoke the applet explicitly:
exec -a ugrep "$CLAUDE_CODE_EXECPATH" -G -I -o -h PATTERN FILE
Why the pattern is ordinary
[^.]{0,90}PHRASE[^.]{0,90}\. means "give me the whole sentence containing this phrase", and n≈90 is simply half a typical sentence. It is the standard idiom for corpus and prose analysis. Across 8,144 grep calls in this user's transcripts, the days with zero such patterns had zero incidents — including a day with 3,769 grep calls — while the days with 20+ froze the machine. The trigger is the pattern shape, not workload volume.
Requests
- Bound the applet's memory, and fall back to the system
grep(or a streaming matcher) when the bound is exceeded. A slow correct search is strictly better than an OOM. - Make the substitution visible.
ps/OOM output showing a bare version number is very hard to attribute. Settingcommto something likeclaude-ugrepwould have saved days of investigation here. - Consider whether
grepshould be shadowed silently at all, given that users and agents write patterns against GNU grep's performance expectations. - Track the upstream ugrep issue, since the root cause is in the matcher.
Note on subagents
The runaways in this environment came predominantly from workflow subagents' Bash calls. A documented user-level rule (CLAUDE.md) does not appear to reach subagents — 0 of 40 subagent transcripts inspected contained the global CLAUDE.md text, while a top-level session transcript did. So user-side documentation cannot mitigate this on the path where it actually occurs; it needs handling inside the tool.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗