Bash tool `grep` (bundled ugrep) silently misses `^` alternatives: `(a|b|^)x` and `(^|[^0-9])x` match nothing that GNU grep matches
Environment: Claude Code 2.1.296, Linux aarch64, bash. Inside the Bash tool, grep is the shell function Claude Code
injects, which runs the bundled ugrep 7.8.4 (ugrep --version through the same binary).
What happens: some -E patterns with ^ inside a group return no match, with exit status 1 and no warning, where
GNU grep matches. An agent that counts or checks absence with such a pattern gets a wrong answer it cannot detect.
printf 'fail 1\nx; fail 2\nfoo\n' > f.txt
grep -cE '(a|b|^)fail' f.txt # 0 (GNU grep: 1) ^ as the third alternative
grep -cE '(;|&&|^)fail' f.txt # 0 (GNU grep: 1)
grep -cE '(a|^|b)fail' f.txt # 1 (GNU grep: 1) ^ first or second works
command grep -cE '(a|b|^)fail' f.txt # 1 (/usr/bin/grep)
printf '5798-72bbf60eb5b8.json\n' > g.txt
grep -cE '(^|[^0-9])5798' g.txt # 0 (GNU grep: 1) the shape in #98421
Where the bug is: in ugrep's regex engine, not in Claude Code's wrapper flags. Running the bundled binary asugrep -E alone (without the wrapper's -G --ignore-files --hidden -I --exclude-dir=...) gives the same 0, and
upstream ugrep v7.8.5 (the latest release, built from source) gives the same results for every pattern above. $ in
the same positions works. Filed upstream as https://github.com/Genivia/ugrep/issues/573.
Why it matters here: the shadowing is silent. A user or an agent that types grep expects GNU grep's dialect; the
injected function swaps the engine without saying so, so a wrong count looks like a real answer. In our case a count
recorded from the Bash tool was wrong for weeks, always by exactly the line-initial matches, until someone re-ran it with/usr/bin/grep.
Asks (any of these would have prevented it):
- Document in the Bash tool's description (and
/help) thatgrepis ugrep, and thatcommand grepreaches the
system grep.
- An opt-out for the grep/find shadow functions (#69736, #91040).
- Until ugrep is fixed upstream, fall back to the system grep for
-E/-Gpatterns that put^in a group (or warn). - Bump the bundled ugrep once upstream ships a fix.
Related: #98421 (same engine family, (^|[^0-9]) shape), #69736 and #91040 (opt-out requests), #80103 (bundled search
wrappers narrow silently and exit 0).