[BUG] Embedded ugrep wrapper hangs (100% CPU, unbounded memory growth) on bounded-repeat regexes; pattern-compilation blowup reproducible on 2-byte input
Summary
The grep shell wrapper introduced in v2.1.117 (which re-execs the Claude Code binary
as ugrep) hangs indefinitely on regexes containing two large bounded repeats of
negated character classes, for example [^>]{0,100}word[^<]{0,200}. The hang is in
pattern compilation, not matching: it reproduces with a 2-byte stdin input. While
hung, the process burns ~100% CPU and its RSS grows without bound (~100 MB/s observed),
so an unattended hang also threatens the host with OOM, the same amplification effect
reported in #54394.
The shape is one a coding agent naturally writes when slicing HTML or logs, and GNU
grep handles it instantly, so the model has no signal that the pattern is dangerous.
Combined with #45717 (Bash-tool timeout kill propagating to the whole CLI), the
end-to-end effect on WSL2 is: an innocent-looking grep hangs, the 120 s timeout fires,
the entire Claude Code process dies (exit 143/144), the terminal session closes, and
WSL idle-shutdown then kills background agents and wipes tmpfs. This chain caused
repeated unexplained "disconnects" here before it was diagnosed.
Environment
- Claude Code: 2.1.173 (also reproduced on earlier 2.1.x on 2026-06-02)
- Embedded engine self-identifies as:
ugrep 7.5.0 x86_64-pc-linux-gnu +sse2; -P:pcre2jit; -z:zlib,... - Platform: WSL2 (kernel 6.18.33.1-microsoft-standard-WSL2), Ubuntu
- Comparison baseline: GNU grep 3.11
Minimal reproduction (no downloads, 2-byte input)
In a Claude Code Bash session (where grep is the shell-snapshot wrapper function):
echo x | grep -E -o '[^>]{0,100}credential[^<]{0,200}' # hangs forever, ~100% CPU
echo x | command grep -E -o '[^>]{0,100}credential[^<]{0,200}' # returns instantly
Equivalent direct invocation of the embedded engine, replicated from the wrapper
function's own exec line:
echo x | timeout 10 bash -c \
"exec -a ugrep ~/.local/bin/claude -I -E -o '[^>]{0,100}credential[^<]{0,200}'"
echo $? # 124 (timed out); never completes regardless of timeout length tried
Because the hang occurs with effectively no input, this is automaton construction
blowing up on the pattern itself; input-side mitigations cannot help.
Characterization (each cell verified with timeout guards)
| Variant | Result |
|---|---|
| [^>]{0,100}(a\|b\|c)[^<]{0,200} on 52 KB single-line file | hangs (>30 s) |
| Same pattern without -o | hangs |
| [^>]{0,100}word[^<]{0,200} (no alternation) | hangs; alternation is not required |
| (a\|b\|c) alone | fine |
| Single bounded repeat on either side of the core | fine |
| [^>]{0,10}...[^<]{0,20} (small bounds) | fine |
| {0,50} + {0,50} | fine |
| {0,50} + {0,100} | hangs; the product of the two bounds appears to matter |
| Same pattern with -P (pcre2jit) | completes instantly; only the default engine path is affected |
| GNU grep -Eo, same pattern, 52 KB file | 0.22 s, 177 matches |
Resource behavior while hung
/usr/bin/time -v on the guarded repro (2-byte input):
- 6 s wall: 5.25 s user CPU, max RSS 828 MB
- 15 s wall: max RSS 1.45 GB
That is roughly 100 MB/s of unbounded allocation. On a default WSL2 VM this OOMs the
guest within a few minutes if nothing kills the process, independent of the Bash-tool
timeout, which matches the V8-heap amplification described in #54394.
Relationship to existing issues
- #54394 reports the same component (embedded ugrep inside the claude binary, V8
heap amplification) but a different trigger: runtime backtracking of unbounded
[^"]* alternations against multi-MB single-line inputs. This report adds a second,
input-independent trigger class: bounded-repeat compilation blowup, with a 2-byte
repro and a bound-size threshold. Both argue for the same class of fix.
- #45717 (timeout kill propagation) converts this hang from "one lost command"
into "the whole CLI and its background agents die", which is what makes the bug
operationally severe.
- #59517 (wrapper forcing
-G) is adjacent wrapper behavior but not the cause
here; the hang reproduces with -E explicitly.
Suggested fixes (any one contains the damage)
- Cap the matcher: run embedded grep invocations under an RSS rlimit and a wall-clock
guard, failing the command with a clear error instead of hanging (also proposed in
#54394).
- Guard pattern compilation specifically: a counted-repeat expansion limit (or
compile-time budget) that falls back to the PCRE2 path, which handles these
patterns instantly per the -P result above.
- Replace the engine for the grep shim with a non-pathological one (RE2/ripgrep
class), as #54394 suggests.
- Independently, fixing #45717 would stop a hung matcher from killing the CLI.
Workarounds for affected users
- Use
command grepfor anything with bounded repeats, multi-alternations, or large
single-line inputs; the wrapper is fine for simple literal searches.
- Wrap risky background dispatches in
setsid -fso a timeout kill cannot reach the
CLI's process group (from the #45717 thread); note a detached hung matcher then
survives and must be killed manually before it OOMs the host.
Note on upstream
The engine self-identifies as ugrep 7.5.0. I have not verified whether stock Genivia
ugrep 7.5.0 reproduces the compilation blowup; if it does, an upstream report to
Genivia/ugrep may also be warranted, but the operational severity here (timeout kill
propagation, V8 amplification) is specific to the Claude Code embedding.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗