[BUG] Embedded ugrep wrapper hangs (100% CPU, unbounded memory growth) on bounded-repeat regexes; pattern-compilation blowup reproducible on 2-byte input

Resolved 💬 2 comments Opened Jun 11, 2026 by scooter-tecsys Closed Jun 15, 2026

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)

  1. 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).

  1. 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.

  1. Replace the engine for the grep shim with a non-pathological one (RE2/ripgrep

class), as #54394 suggests.

  1. Independently, fixing #45717 would stop a hung matcher from killing the CLI.

Workarounds for affected users

  • Use command grep for 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 -f so 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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗