[BUG] Shell grep shim: BRE alternation \| combined with literal ( produces 'rg: regex parse error: unclosed group'
Summary
The shell-snapshot grep function routes grep invocations to the embedded ugrep-style BRE emulation (backed by ripgrep's Rust regex engine). Its BRE-to-Rust-regex translation breaks when a pattern combines BRE alternation (\|) with a literal unescaped (: the alternation is converted and wrapped in (?:...), but the literal paren is not escaped, producing an invalid regex.
Each feature works fine on its own — the combination fails.
Environment
- Claude Code v2.1.199
- macOS 26.5 (Darwin 25.5.0), zsh
Minimal repro
Inside any Claude Code Bash tool session:
$ printf 'foo\nbar\n' > /tmp/greptest.txt
$ grep -n "foo\|bar" /tmp/greptest.txt # BRE alternation alone: works
1:foo
2:bar
$ grep -n "foo(" /tmp/greptest.txt # literal paren alone: works (no match, exit 1, no error)
$ grep -n "foo(\|bar" /tmp/greptest.txt # combination: parse error
rg: regex parse error:
(?:foo(|bar)
^
error: unclosed group
0 matches for 'foo(\|bar'
The translated pattern shown in the error, (?:foo(|bar), makes the bug visible: \| was correctly converted to | and the branches wrapped in a non-capturing group, but the literal ( in the first branch was left unescaped, so it opens a group that never closes.
Expected behavior
Under BRE semantics (the shim runs with -G), an unescaped ( is a literal character. grep "foo(\|bar" should translate to (?:foo\(|bar) and match lines containing foo( or bar — as /usr/bin/grep does:
$ command grep -n "foo(\|bar" /tmp/greptest.txt
2:bar
Actual behavior
Exit code 2 with rg: regex parse error: ... unclosed group.
Impact
This surfaces in real usage when the model greps for function-call sites with multiple alternatives, e.g.:
grep -n "\.matches(\|card_to_pydict(\|run_query_linear(" src/lib.rs
fails with the same unclosed-group error, and the rg: prefix in the message misleads users (and the model) into blaming a local ripgrep alias rather than the shim.
Notes
- Likely the same translation applies to other BRE metacharacters (
), and possibly{/}) when combined with\|— the paren case is the one verified. - Related but distinct: #54394 concerns the same embedded ugrep wrapper but a different failure (regex backtracking OOM).