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)

Status Open
Reported on v2.1.290
Maintainer reply None cached
Activity 0 comments · opened Oct 6, 2026

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.

View original on GitHub ↗