Shell-snapshot grep/find shadowing has no opt-out, and --ignore-files makes gitignored content silently invisible

Status Open
Reported on v2.1.251
Maintainer reply None cached
Activity 0 comments · opened Aug 31, 2026

Environment

  • Claude Code 2.1.251, macOS (zsh)

What happens

The generated shell snapshot (~/.claude/shell-snapshots/snapshot-zsh-*.sh) contains a block — "Shadow find/grep with embedded bfs/ugrep" — that redefines grep and find as functions exec'ing the claude binary's embedded ugrep/bfs with extra flags: -G --ignore-files --hidden -I --exclude-dir=.git.

Two consequences:

  1. --ignore-files honors .gitignore, so grep -rn <pattern> . over a repo returns 0 matches at exit 1 for content that exists in a gitignored file (build output, .env.example copies, vendored dirs, logs). Combined with -I (skip binaries), a search that "finds nothing" is indistinguishable from the content not existing. In an agent loop that's the dangerous shape: exit 1 + empty output reads as not present, and the model draws the opposite of the true conclusion. We've hit this in practice — one session concluded content was absent that a plain grep finds immediately.
  2. Some invocation shapes get mangled — a large newline-joined multi-file argument list (from $(grep -rl ...)) produced ugrep: warning: ... File name too long per file and reported 0 matches where matches existed.

What I looked for

  • USE_BUILTIN_RIPGREP=0 only switches which rg is used; it does not gate the grep/find shadow.
  • The snapshot generator appears to emit the block unconditionally for this build, and the shadow functions are defined after profile content, so a user-defined grep function in .zshrc is overridden.
  • No documented setting or env var found that disables it.

Workaround

command grep / command find bypass the shadow. That works but has to be remembered per-invocation, including by the model itself.

Request

An opt-out — e.g. CLAUDE_CODE_DISABLE_SHELL_SHADOWING=1 (or a granular equivalent, or a settings.json key) that stops the snapshot from shadowing grep/find. Alternatively, dropping --ignore-files from the injected flags would remove the worst hazard while keeping the token-savings intent: a search tool that silently excludes gitignored files changes the meaning of exit 1, not just the output size.

🤖 Filed with Claude Code on the user's behalf.

View original on GitHub ↗