[Bug] grep shell shim silently skips binary and ignored files, masking search failures from agents
Bug Description
# grep shell shim returns exit 1 with zero output on binary/ignored files — silent false negatives mislead agents
## Environment
- Claude Code on Linux (WSL2)
- Bundled ugrep 7.5.0; system GNU grep 3.11 present at /usr/bin/grep
## What happens
Claude Code installs a shell function named grep that shadows the system grep in Bash tool
calls. It runs the bundled ugrep with hardcoded flags:
``
exec -a ugrep "$CLAUDE_CODE_EXECPATH" -G --ignore-files --hidden -I \
--exclude-dir=.git --exclude-dir=.svn --exclude-dir=.hg \
--exclude-dir=.bzr --exclude-dir=.jj --exclude-dir=.sl "$@"
`
Two of those flags cause files to be skipped silently:
- **-I** skips any file containing a NUL byte. A source file with a literal NUL in a string
literal (legal in JS, Python, and others — e.g. a delimiter in a composite map key) is
therefore classified binary and never searched.
- **--ignore-files** skips anything matched by a .gitignore, and honors .gitignore even
outside a git repository (ripgrep, by contrast, only applies it inside one).
In both cases the result is **exit status 1 with zero bytes on stdout *and* stderr** — byte
identical to a genuine "no match found."
## Reproduction
Binary/NUL vector:
`sh
cd "$(mktemp -d)"
printf 'alpha needle\nconst SEP = "\000";\nbeta needle\n' > withnul.js
grep -c needle withnul.js # no output, exit 1 <-- silent false negative
grep -a -c needle withnul.js # 2, exit 0
/usr/bin/grep -c needle withnul.js # 2, exit 0
`
Ignore-file vector:
`sh
mkdir sub && printf 'gamma needle\n' > sub/hit.js && printf 'sub/\n' > .gitignore
grep -rn needle . # sub/hit.js absent, no warning
grep -rn --no-ignore-files needle . # sub/hit.js present
`
## Why this is worse for an agent than for a human
1. **exit 1 + empty output is the wire encoding of "I looked and found nothing."** Here it
means "I declined to look." A caller cannot distinguish the two, so an agent concludes the
code does not exist.
2. **The shim defeats self-diagnosis.** which grep reports /usr/bin/grep; grep --version
reports ugrep; neither reveals that a shell function is intercepting the call. An agent
investigating its own failed search gets contradictory answers and tends to misattribute the
cause to the machine's configuration rather than to Claude Code.
3. **Observed downstream harm.** An agent ran several searches against a file containing a stray
NUL byte, received silent zeros, and concluded that a feature's wiring did not exist. It then
generalized that into a durable but incorrect lesson about the environment. A single wrong
search result is recoverable; a wrong heuristic carried forward into later sessions is not.
The failure mode actively manufactures confident false claims.
## Tools that handle the identical file honestly
| Tool | Result |
|---|---|
| GNU grep 3.11 | prints binary file matches; -c returns correct count; exit 0 |
| git grep | correct count, exit 0 |
| rg on an explicit file path | correct count, exit 0 |
| sed, awk, wc, sort, python, node | unaffected — NUL is just a byte |
| rg recursive | also skips silently (same class of issue) |
## Suggested fix
The flags themselves are reasonable defaults for interactive use — nobody wants binary spewed
into a terminal, and respecting .gitignore is usually right. The problem is that the skip is
invisible. Please make it observable rather than removing the behavior:
- Emit a stderr note when files are skipped, e.g. ugrep: skipped 2 files (1 binary, 1 ignored),
mirroring GNU grep's long-standing binary file matches; **or**
- Return a distinct exit status when the only reason for an empty result was a skip.
Either preserves current default output for humans while letting a programmatic caller
distinguish *no match* from *not searched*.
## Workarounds (for anyone else hitting this)
- grep -a overrides the hardcoded -I
- grep --no-ignore-files overrides the ignore-file skip
- command grep bypasses the shell function entirely
- arguments containing -z, -Z, or --null fall through to the system grep
## Minor related note
Bash cannot pass a literal NUL as an argument, so $'\x00' collapses to an **empty pattern that
matches every file** — a sweep for NUL-bearing files needs grep -P '\x00' or a script, not a
$'\x00'` literal. This is easy to get wrong and produces a confidently bogus file list.
Environment Info
- Platform: linux
- Terminal: vscode
- Version: 2.1.233
- Feedback ID: 21c450b9-0f1d-4024-ad98-e9e66608444c
Errors
[]This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗