[Bug] grep shell wrapper silently overrides -E (ERE) with -G (BRE) via ugrep — breaks scripts using extended regex
Summary
Claude Code's shell environment overrides grep with a bash function that routes all calls through the Claude Code binary using exec -a ugrep. The wrapper unconditionally passes -G (basic regular expressions / BRE) to ugrep, regardless of what flags the caller specified. When a script passes -E (extended regular expressions / ERE), the -G override causes the regex to be interpreted as BRE, producing wrong results with no error message.
In non-interactive subshell contexts ($BASHPID != $$), the exit code returned is 127, making the call appear as "command not found" rather than a regex mismatch.
This silently breaks any script that uses grep -E in the Claude Code shell — the command returns incorrect results or exits 127 without any visible indication that the regex engine changed.
This is distinct from #57362 (the $ZSH_VERSION unbound variable issue, which is cosmetic). This is a correctness bug: wrong regex engine, wrong results, no error.
---
Repro
# 1. Confirm grep is overridden and -G is hardcoded
declare -f grep | grep '\-G'
# Output shows: exec -a ugrep "$_cc_bin" -G --ignore-files ... "$@"
# -G is hardcoded before "$@" — your -E is ignored
# 2. ERE alternation fails silently
echo "requirements-dev.txt" | grep -qE '(^|[-_.])(dev)([-_.]|$)' && echo "MATCH" || echo "NO MATCH"
# Expected: MATCH
# Actual: NO MATCH (BRE interprets | as literal, not alternation)
# 3. ERE character class fails silently
echo "foo-bar" | grep -qE 'foo[-_]bar' && echo "MATCH" || echo "NO MATCH"
# Expected: MATCH
# Actual: NO MATCH or exit 127 in subshell
# 4. command grep works correctly, confirming the wrapper is the cause
echo "requirements-dev.txt" | command grep -qE '(^|[-_.])(dev)([-_.]|$)' && echo "MATCH" || echo "NO MATCH"
# Output: MATCH
# 5. Exit code 127 in subshell context
result=$(echo "test-file.txt" | grep -qE '[-_]'; echo $?)
echo "exit: $result"
# Expected: 0 (match found)
# Actual: 127 (exec -a ugrep failed in subshell)
---
Wrapper (from declare -f grep)
grep ()
{
local _cc_bin="${CLAUDE_CODE_EXECPATH:-}";
[[ -x $_cc_bin ]] || _cc_bin=/home/user/.local/bin/claude;
...
if [[ $BASHPID != $$ ]]; then
exec -a ugrep "$_cc_bin" -G --ignore-files --hidden -I --exclude-dir=.git ... "$@";
else
( exec -a ugrep "$_cc_bin" -G --ignore-files --hidden -I --exclude-dir=.git ... "$@" );
fi
}
The -G flag is hardcoded before "$@". ugrep uses the last-specified mode flag — when the caller passes both -G and -E, BRE wins, silently switching the engine.
---
Expected behavior
grep -E 'pattern' in the Claude Code shell should use extended regular expressions. If the wrapper routes through ugrep, it should respect the caller's -E/-P/-F flags rather than hardcoding -G.
Minimal fix: detect the caller's regex mode flag before adding -G:
for _cc_a in "$@"; do
case "$_cc_a" in
-E|-P|-F|-G|--extended-regexp|--perl-regexp|--fixed-strings|--basic-regexp)
# Caller specified a mode — pass through without forcing -G
exec -a ugrep "$_cc_bin" --ignore-files --hidden -I --exclude-dir=.git ... "$@"
return ;;
esac
done
# No mode flag — safe to default to -G
exec -a ugrep "$_cc_bin" -G --ignore-files --hidden -I --exclude-dir=.git ... "$@"
---
Impact
- Any bash script sourced or executed in the Claude Code shell that uses
grep -Esilently produces wrong results - ERE features that break under BRE: alternation
(a|b),+,?,{n,m}, character class shortcuts - No error is printed — the command appears to run but produces incorrect output
- Exit code
127in subshell context mimics "command not found," not a regex failure - Workaround is non-obvious:
unset -f greporcommand grep -E
Environment
- Claude Code CLI (claude-sonnet-4-6)
- Shell: bash
- OS: Ubuntu 24 (Linux 6.14.0)
- Reproduces in any Bash tool call that sources a bash script using
grep -E
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗