[BUG] grep shim invokes bundled ugrep with forced -G, causing 20+ GB memory blowup from KB-sized piped input
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
The grep shell-function shim that Claude Code installs in its bash shell-snapshot can cause the bundled ugrep binary to consume 20+ GB of RAM from only a few KB of piped input, exhausting the machine and crashing the host (a full WSL2 VM crash requiring a machine restart, in my case).
Claude Code defines grep as a shell function that execs its own binary as ugrep with forced flags:
grep () { … exec -a ugrep "$_cc_bin" -G --ignore-files --hidden -I --exclude-dir=.git --exclude-dir=.svn … "$@"; }
When an agent runs an ordinary pipeline such as:
curl -s "$URL" | sed 's/<[^>]*>//g' | grep -oE '.{0,40}(cents|per gallon|[0-9]{2}\.[0-9]).{0,40}'
the forced -G (basic-regex) collides with the user's -oE (extended-regex), and combined with the only-matching flag -o and bounded .{0,N} quantifiers, the resulting ugrep process balloons without bound.
Observed live: a single-threaded ugrep process (exe -> …/claude/versions/2.1.179, comm 2.1.179) read 7,281 bytes from stdin (/proc/<pid>/io: rchar: 7281, read_bytes: 0 — it was reading the pipe, not the filesystem) yet grew linearly to 21.2 GB RSS in ~70 seconds before I killed it. With vm.overcommit_memory=1 and disk-backed swap, this
took the whole 32 GB VM down.
The same pattern run through /usr/bin/grep completes instantly with flat memory, confirming the blowup is specific to the shimmed ugrep + flag combination, not the regex or the input.
What Should Happen?
A grep over a few KB of piped stdin should complete in milliseconds using negligible memory, exactly as /usr/bin/grep does. The ugrep shim should not balloon to tens of GB — at minimum, the forced -G flag should not silently combine with a user-supplied -oE into a pathological/unbounded-memory state, and ideally the shim should not
override regex-type flags the user explicitly passed.
Error Messages/Logs
No error is emitted — the process simply consumes all memory until the OOM/VM crash. Captured evidence:
# runaway process (single thread, reading a pipe, 21 GB RSS):
PID PPID RSS(kB) %CPU COMM
54644 54621 21256576 109 2.1.179
exe -> /home/ubuntu/.local/share/claude/versions/2.1.179
cmdline: ugrep -G --ignore-files --hidden -I --exclude-dir=.git --exclude-dir=.svn \
--exclude-dir=.hg --exclude-dir=.bzr --exclude-dir=.jj --exclude-dir=.sl \
-oE .{0,40}(cents|per gallon|[0-9]{2}\.[0-9]).{0,40}
# /proc/54644/io -> read only 7 KB of stdin while at 19 GB RSS:
rchar: 7281
read_bytes: 0
# system memory just before kill:
used=30730MB avail=1367MB (32 GB VM)
# immediately after killing the one process:
used=8823MB avail=23275MB
Steps to Reproduce
- On a system where Claude Code has installed its grep shell-function shim (verify with type grep — it should be a function that execs … -a ugrep … -G --ignore-files --hidden …).
- Run a pipeline that filters piped stdin with an extended, only-matching, bounded-quantifier pattern, e.g.:
curl -s https://example.com/ | sed 's/<[^>]*>//g' | grep -oE '.{0,40}(foo|bar).{0,40}'
- Watch the resulting ugrep process RSS (e.g. watch -n1 'ps -eo rss,comm --sort=-rss | head').
- Observe RSS climbing into the multi-GB range from a tiny input. Compare with … | /usr/bin/grep -oE '.{0,40}(foo|bar).{0,40}', which returns instantly with no memory growth.
(To reproduce safely, cap memory first: run inside a subshell with ulimit -v 2000000 so the process aborts at ~2 GB instead of taking down the host.)
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.179 (Claude Code)
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
WSL (Windows Subsystem for Linux)
Additional Information
- Environment: WSL2 VM, .wslconfig memory=32GB / processors=8, default 8 GB disk-backed swap, vm.overcommit_memory=1. The combination of overcommit + disk swap is why a single runaway process crashes the entire VM (vmmem hangs) rather than triggering a clean per-process OOM-kill.
- Likely trigger: the shim unconditionally injects -G (BRE). When the user/agent also passes -E/-oE, the conflicting regex-type flags plus -o with bounded .{0,N} repetition appears to drive ugrep into unbounded memory allocation.
- Workaround: in piped pipelines, call /usr/bin/grep, command grep, or rg instead of bare grep (especially with -o + .{0,N} patterns). Recursive file search (grep -rl …) is unaffected.
- The grep shim function (from the shell-snapshot), for reference:
grep () {
local _cc_a
for _cc_a in "$@"; do case "$_cc_a" in
--filter|--pager|--view|--format-open|--config|---|-@|--save-config)
command grep "$@"; return ;;
esac; done
local _cc_bin="${CLAUDE_CODE_EXECPATH:-}"
[[ -x $_cc_bin ]] || _cc_bin=/home/ubuntu/.local/bin/claude
…
exec -a ugrep "$_cc_bin" -G --ignore-files --hidden -I \
--exclude-dir=.git --exclude-dir=.svn --exclude-dir=.hg \
--exclude-dir=.bzr --exclude-dir=.jj --exclude-dir=.sl "$@"
}
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗