Grep tool returns silent "no matches" for files containing a NUL byte (swallows ripgrep's "binary file matches" notice)
Summary
If a text source file contains a raw NUL byte (0x00), the Grep tool returns a silent "no matches" — even when the pattern is present. ripgrep (the bundled backend) actually does find the match and prints a binary file matches (found "\0" byte ...) notice, but the Grep tool's output layer discards that line and surfaces nothing. The result is a correctness trap: an agent reads "no matches" as "this code does not exist in the codebase" and misdiagnoses accordingly.
This cost ~5 wasted tool calls in a real session before a manual rg in Bash revealed the binary-file notice that the Grep tool had hidden.
Environment
- Backend: ripgrep bundled in the package —
@anthropic-ai/claude-code/vendor/ripgrep/<arch>/rg(v14.1.1 here) - Platform: Linux x86_64 (also reproduces against host ripgrep 14.1.0 — behavior is identical, so this is not a binary-version issue)
Reproduction
# create a .ts file with a raw NUL and a clearly-present token
printf 'hello SIGNATURE_TOKEN here\x00more text\n' > nultest.ts
Then in Claude Code:
Grep: pattern="SIGNATURE_TOKEN", path="nultest.ts", output_mode="content"
Expected: the match, or at minimum a notice that the file was treated as binary.
Actual: "No matches found" — no match, no binary notice, no warning.
Why it happens (verified)
Running the bundled ripgrep directly shows it finds the match and writes a summary line to stdout (exit 0):
$ rg SIGNATURE_TOKEN nultest.ts
binary file matches (found "\0" byte around offset 26) # <- stdout, exit 0
In content mode ripgrep replaces the match lines with that single binary file matches… line. The Grep tool parses stdout expecting structured path:line:text records; the binary file matches… line doesn't fit that shape, so it gets dropped and the user sees nothing.
Confirmed:
- The notice is on stdout, not stderr, with exit 0.
rg -l(files-with-matches) prints the filename andrg -a/--textprints the real lines — so the information is available, the tool just isn't surfacing it.- Note: plain
grepcannot reliably find NUL either (it treats NUL as a line separator), so the obvious double-check also misleads —rgis the only easy way to discover the cause.
Suggested fix (any one helps)
- Pass through the notice — when ripgrep emits
binary file matches, surface it (e.g.nultest.ts: binary file — matched but not shown (contains NUL byte); re-run with text mode) instead of dropping the line. - Auto-fall-back to
--textwhen a binary/NUL file matches, and label the results as coming from a binary file. - At minimum, distinguish "no matches" from "matched in a binary file, suppressed" in the tool's output so an agent doesn't read silence as absence.
The core problem is that silent empty-on-binary is indistinguishable from a genuine zero result, which directly causes wrong conclusions.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗