Bash tool: snapshot-injected ugrep/bfs/rg shim uses bare exec in subshell contexts — statements after the first bare call silently never run

Resolved 💬 0 comments Opened Jun 11, 2026 by BradFEDCON Closed Jun 16, 2026

Environment

  • Claude Code 2.1.170 (VS Code extension 2.1.172, native binary), macOS (darwin arm64), bash
  • Reproduced consistently in the Bash tool of an interactive session

Summary

The Bash tool's shell loads shimmed grep, find, and rg shell functions from ~/.claude/shell-snapshots/snapshot-bash-*.sh (routing to the claude binary's embedded ugrep/bfs/rg via ARGV0=..., variables prefixed _cc_). In the bash branch, when [[ $BASHPID != $$ ]] the function runs a bare exec -a ugrep "$_cc_bin" ... instead of the parenthesized ( exec ... ) form.

The Bash tool appears to execute commands in a context where BASHPID != $$, so the bare-exec branch is taken — and exec replaces the shell that is running the user's compound command. Result: in any multi-statement command, everything after the first bare grep/find/rg silently never executes.

Repro

Inside a Claude Code session, run via the Bash tool:

echo START; grep -in 'clean' /tmp/somefile.txt; echo END

Observed: output is START + the grep matches. END never prints. The tool's reported exit code is grep's exit code (so a no-match grep makes the whole command "fail" with exit 1, and any statements after it — including file writes and cleanup — never run).

Expected: all three statements run; END prints.

Control cases that behave correctly:

  • echo START; command grep -in 'clean' /tmp/somefile.txt; echo END → all statements run (bypasses the function)
  • Plain pipelines (ls | grep x) and a grep as the sole/last statement are unaffected
  • Same breakage with find (bfs shim) and rg: find ~ -name x -maxdepth 1; echo afterafter never prints

type grep inside the tool shell confirms it's a function; the bash branch contains:

if [[ $BASHPID != $$ ]]; then
    exec -a ugrep "$_cc_bin" -G --ignore-files --hidden -I --exclude-dir=.git ... "$@";
else
    ( exec -a ugrep "$_cc_bin" -G --ignore-files --hidden -I --exclude-dir=.git ... "$@" );
fi

Impact

This is a silent failure: partial output, half-written redirect files (a { ...; } > file block stops mid-write), and misleading exit codes. In our case it caused a model session to draw false conclusions from truncated find/grep results (a file declared "doesn't exist" because the second statement of a compound never ran). It also seems likely to be a hidden cause of flaky "command failed" reports wherever models compose grep/find into multi-step commands.

Suggested fix

Use the subshell-wrapped ( exec ... ) form unconditionally (the BASHPID != $$ special case is only safe when the subshell contains nothing but the grep call, which isn't true when the whole compound script is already running in a subshell), or call the shim binary without exec.

Workaround we're using

A PreToolUse hook that blocks bare grep/find/rg in multi-statement commands and instructs the model to use command grep / command find.

View original on GitHub ↗