[Bug] Bash-tool grep wrapper still silently returns nothing on text files with a stray NUL byte (2.1.220) — #56644 was auto-closed as stale, not fixed
Summary
The grep shell function injected into every Bash-tool snapshot invokes bundled ugrep with -I (skip binary). A single NUL byte anywhere in an otherwise-UTF-8 text file makes ugrep classify the whole file as binary and skip it — zero output, exit 1, no diagnostic.
This is the same defect as #56644, which was closed by github-actions[bot] as not_planned with a stale label on 2026-06-21 — auto-closed for inactivity rather than fixed or rejected on merit. It still reproduces on 2.1.220, ~3 months after that report.
Filing fresh rather than commenting on a bot-closed issue, and adding evidence that widens the blast radius.
Reproduction (verified today on 2.1.220)
printf 'hello\0world\nmatch line\n' > /tmp/nul-repro.txt
grep match /tmp/nul-repro.txt
# → no output, exit 1
/usr/bin/grep match /tmp/nul-repro.txt
# → "Binary file /tmp/nul-repro.txt matches", exit 0
What's new since #56644
That report's NUL came from Claude Code's own shell snapshot, inherited from a zsh plugin — which reads as a niche, self-inflicted case.
Mine came from an ordinary source file in a working repo: a .mjs file with one NUL at byte 20429, inside a comment documenting a protocol that uses a null separator. Nothing exotic, no plugin involved, and file(1) calls it text.
grep -c import whatsapp/whatsapp_bridge.mjs # → nothing, exit 1
/usr/bin/grep -c import whatsapp/whatsapp_bridge.mjs # → 10
So this isn't limited to snapshot files. Any user file containing a stray NUL — a comment like mine, a fixture, a test vector, a log, a minified asset — becomes invisible to search with no signal that anything was skipped.
Why this is worse than an ordinary bug
The consumer is an autonomous agent, and an empty result is indistinguishable from a true negative. There is no error to notice and no exit code that differs from "no matches". An agent asked "is X referenced anywhere?" gets silence and reports "no, it isn't" — confidently, and wrongly.
It is also intermittent by construction: clean files search perfectly, so the tool earns trust and then quietly breaks it on one file. In my case it produced two false conclusions in a working session before I tested the checker itself rather than the thing being checked.
Suggested fixes (from #56644, still applicable)
- Drop
-I— match GNU/BSD grep default (printsBinary file X matches, exit 0). - Or replace
-Iwith--binary-files=text. - Or, at minimum, emit a stderr warning on skip so the caller sees
skipped binary file Xinstead of silence.
(3) alone would remove the "silent" property, which is the dangerous part.
Workaround
/usr/bin/grep or command grep bypasses the function. \grep does not — backslash suppresses alias expansion, not function lookup.
Environment
- Claude Code 2.1.220, native macOS arm64 (
CLAUDE_CODE_EXECPATH=/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe) - macOS arm64, zsh
- Wrapper as injected:
ARGV0=ugrep "$_cc_bin" -G --ignore-files --hidden -I --exclude-dir=.git ... "$@"
Related: #59517 (wrapper forces -G, breaking -E), #60325 (ARGV0 dispatch broken), #66804 (no-match killed the tool shell) — all the same injected function.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗