Bundled ugrep shadowing `grep` can exhaust system memory — froze a 16 GB machine twice

Status Fixed / completed
Reported on v2.1.233
Maintainer reply None cached
Activity 2 comments · opened Aug 18, 2026 · closed Aug 20, 2026

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.233 and 2.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 = 0kB from the first kill onward; page cache reduced to ~1 MB, so every process including systemd was 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

  1. 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.
  2. Make the substitution visible. ps/OOM output showing a bare version number is very hard to attribute. Setting comm to something like claude-ugrep would have saved days of investigation here.
  3. Consider whether grep should be shadowed silently at all, given that users and agents write patterns against GNU grep's performance expectations.
  4. 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.

View original on GitHub ↗

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