[Bug] grep shell shim silently skips binary and ignored files, masking search failures from agents

Status Closed — duplicate
Reported on v2.1.233
Maintainer reply None cached
Activity 1 comment · opened Aug 17, 2026 · closed Aug 17, 2026

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

[]

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗