Shimmed grep (embedded ugrep) allocates until host OOM on bounded-quantifier patterns
Summary
Claude Code installs a shell function that silently replaces grep with the ugrep binary embedded in the claude executable. On patterns containing two wide bounded quantifiers (e.g. .{0,60}(…).{0,300}), ugrep's automaton construction allocates ~235 MB/s linearly until it exhausts host memory (18–20 GB observed), taking the whole machine down.
This crashed my workstation twice (2026-08-08 and 2026-08-15).
The severity is not the OOM itself — it's that the substitution makes the failure undiagnosable. Because the shell function only exists inside a Claude Code session, the natural reproduction step (running the same command in a normal terminal) executes GNU grep and completes in 0.15 s. Every investigation therefore concludes "the command is not the problem" and starts hunting for a memory leak in Claude Code. Two separate sessions spent hours eliminating hooks, subagents, locales, and versions before finding it.
The symptom is also actively misleading: since ugrep runs inside the claude binary (exec -a ugrep "$CLAUDE_CODE_EXECPATH"), the process whose RSS climbs to 20 GB is claude. It looks exactly like a Claude Code memory leak. It isn't one.
Reproduction
# Any small UTF-8 or ASCII text file. Mine was 36 KB of HTML.
PATTERN='.{0,60}(Erbas|10 aout|creche).{0,300}'
# Inside a Claude Code session — bound it, or your machine dies:
( ulimit -v 4000000 -t 25; time grep -c -oiE "$PATTERN" file.html )
# → 17.05s, SIGSEGV (core dumped) after exhausting the 4 GB cap
# Same session, bypassing the shell function:
( ulimit -v 4000000 -t 25; time command /usr/bin/grep -c -oiE "$PATTERN" file.html )
# → 0.148s, correct output
Without ulimit, allocation continues past 18 GB and the host OOMs.
Sometimes ugrep detects it and bails with an error instead — but only after burning the memory:
ugrep: error: error at position 116
rb(?:a|A)(?:s|S))(?:[^\n\x80-\xbf][\x80-\xbf]*){0,300}
exceeds complexity limits
What I isolated
Measured on a 36 KB file, one variable at a time:
| Pattern | ugrep (session) | GNU grep |
|---|---|---|
| .{0,20}(…).{0,200} | 46 ms | 46 ms |
| .{0,60}(…).{0,300} | 17 s → segfault at 4 GB | 148 ms |
- Not UTF-8 specific — a pure-ASCII alternation explodes identically.
- Not the alternation — a single branch explodes identically.
- Not
-i— explodes without it. - It is the product of the two bounds. One wide
.{0,200}alone is fine (46 ms).
Environment
| | |
|---|---|
| Claude Code | 2.1.233 |
| OS | Ubuntu 24.04.4 LTS, kernel 7.0.0-28 |
| RAM | 31 GB |
| Shell | bash |
| Substituted binary | ugrep 7.5.0 x86_64-pc-linux-gnu +sse2 |
| Real grep | GNU grep 3.11 |
The shell snapshot (~/.claude/shell-snapshots/snapshot-bash-*.sh) defines functions for grep, find (→ bfs), and pkill. Relevant part:
function grep {
...
local _cc_bin="${CLAUDE_CODE_EXECPATH:-}"
[[ -x $_cc_bin ]] || _cc_bin=/home/user/.local/bin/claude
if [[ ! -x $_cc_bin ]]; then command grep ${1+"$@"}; return; fi
...
(exec -a ugrep "$_cc_bin" -G --ignore-files --hidden -I --exclude-dir=.git ... ${1+"$@"})
}
Suggestions
Roughly in order of value:
- Cap ugrep's memory. A pattern-compilation blowup should fail that one command, not the host. This alone downgrades the bug from "workstation dies" to "command errors out".
- Provide an opt-out. There is currently no environment variable guarding these functions — the only escape hatch is knowing to type
command grep. Something likeCLAUDE_CODE_NO_COMMAND_SHIMS=1would let users bisect this class of problem in minutes instead of hours. - Make the substitution discoverable. Even a line in
claude doctoroutput ("grep/find/pkill are shimmed to embedded ugrep/bfs in this session") would have saved both investigations. The current behaviour violates the reasonable assumption thatgrepmeans the system's grep. - Consider falling back to
command grepwhen ugrep exits abnormally, so behaviour degrades to "slower" rather than "host down".
Happy to provide the core dump, the full shell snapshot, or memory-growth curves if useful.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗