Bash tool grep shim: -q/-c/-l and output to /dev/null report no match when stdin has one invalid UTF-8 byte, while printing lines works (macOS, 2.1.290)
Summary
In the Bash tool, the injected grep shell function (bundled ugrep run with -I) gives a different answer depending on the output mode when its input contains one invalid UTF-8 byte. Printing matching lines to a pipe or file works. -q, -c, -l, and plain grep with output sent to /dev/null all exit 1 with no output, even though a valid line matches. So grep X and grep -q X disagree on the same input, and an if ... | grep -q check takes the wrong branch silently.
This is the same -I binary-skip class as #56644 (closed, NUL bytes) and #89496, but triggered by invalid UTF-8 rather than NUL, and it splits by output mode within one input. On macOS this matters because system tools emit non-UTF-8 bytes in ordinary output: pmset -g assertions reports a Bluetooth HID device's product name in Mac Roman, so a keyboard whose name contains a typographic apostrophe (’, Mac Roman 0xd5) puts an invalid UTF-8 byte into the output whenever that keyboard is in use.
Environment
- Claude Code 2.1.290, macOS 26.6.2, arm64, zsh
- Embedded engine:
ugrep 7.8.4 aarch64-apple-macosx +neon/AArch64; -P:pcre2jit; -z:zlib,bzip2,zstd,brotli,7z,tar/pax/cpio/zip - Injected flags:
-G --ignore-files --hidden -I --exclude-dir=.git ...
Reproduction (inside the Bash tool)
I='match\ncaf\xd5 menu\n' # line 2 holds one byte 0xd5 (invalid UTF-8)
printf "$I" | grep match | cat # prints "match", grep exit 0
printf "$I" | grep -c match # prints nothing, exit 1 (expected: 1)
printf "$I" | grep -q match # exit 1 (expected: 0)
printf "$I" | grep -l match # prints nothing, exit 1 (expected: (standard input))
printf "$I" | grep -v -c match # prints nothing, exit 1 (expected: 1)
printf "$I" | grep match >/dev/null; echo $? # 1 (expected: 0)
printf "$I" | command grep -c match # 1, exit 0 (system BSD grep)
printf "$I" | grep -a -c match # 1 (-a restores it)
LC_ALL=C does not change the shim's result; -a does.
Measured comparison
| Mode | Bash-tool grep | /usr/bin/grep |
|---|---|---|
| print lines to a pipe or file | correct, exit 0 | correct, exit 0 |
| output to /dev/null | exit 1 | exit 0 |
| -c | no output, exit 1 | 1, exit 0 |
| -q | exit 1 | exit 0 |
| -l | no output, exit 1 | (standard input), exit 0 |
| -v -c, -cF | no output, exit 1 | correct count, exit 0 |
Impact
Agents routinely run checks like some-os-command | grep -q PATTERN or | grep -c PATTERN in the Bash tool. When the command's output contains any non-UTF-8 byte (macOS device names in pmset, ioreg or log show output, process arguments, legacy-encoded files), the check reports "not present" or a count of nothing, with no error. The answer is believable, so it leads to wrong conclusions rather than a visible failure. The same pipeline run by a script (bash script.sh) uses the system grep and gives the right answer, so behavior also differs between the tool and the scripts it runs.
Expected
Either treat invalid UTF-8 on stdin as text (as the system BSD grep does for counting and -q in the table above), or make every output mode agree: if the input is skipped as binary, printing mode should skip it too, and the skip should be reported on stderr rather than silent. An opt-out for the shim (#69736) would also let users avoid it.
Workaround
Use command grep, rg, or grep -a for checks on OS command output in the Bash tool.