claude-code - 💡(How to fix) Fix [BUG] Bash tool: injected grep/find shell functions terminate the calling shell under bash < 4.0 (macOS system /bin/bash)

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

Error Messages/Logs

None — and that is central to the severity. The command succeeds (exit 0) and produces correct output; the shell is simply gone afterward, with no error, warning, or nonzero status. Observed vs. expected:

Root Cause

This report was drafted with Claude's help. This appears to be standard practice in this repository, but I wanted to call it out because I do not normally use LLMs in my written communication.

Fix Action

Fix / Workaround

2. Provide a supported opt-out for the shadowing. The Bash tool monkey-patches the user's shell with functions that shadow grep/find; this is invasive, depends on shell internals (this bug is one failure mode), and currently can't be turned off. The earlier builtin-ripgrep path honored USE_BUILTIN_RIPGREP=0, but a similar escape hatch was never added for the embedded bfs/ugrep functions. A documented env var (a USE_BUILTIN_RIPGREP-style flag, or CLAUDE_CODE_DISABLE_EMBEDDED_SEARCH=1) and/or settings key that suppresses these functions and falls back to system grep/find (and rg when present) would:

  • Make failures like this debuggable — isolating it would be a one-line experiment (disable shadowing → the silent truncation disappears → cause confirmed) and provide an immediate workaround while a fix ships. As things stand, this problem was EXTREMELY confusing to track down!
  • Respect users who prefer their own toolchain.
  • Bound the blast radius of any future shell-shadowing bug to a supported switch rather than a required release.

Code Example

function find {
  ...
  if [[ -n $ZSH_VERSION ]]; then
    ARGV0=bfs "$_cc_bin" -S dfs -regextype findutils-default "$@"
  elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "win32" ]]; then
    ARGV0=bfs "$_cc_bin" ... "$@"
  elif [[ $BASHPID != $$ ]]; then          # $BASHPID is empty on bash < 4.0
    exec -a bfs "$_cc_bin" ... "$@"          #   => always taken => replaces the shell
  else
    (exec -a bfs "$_cc_bin" ... "$@")        #   correct branch, never reached
  fi
}

---

elif [[ ${BASHPID:-$$} != $$ ]]; then

---

$ /bin/bash -c 'echo BEFORE; [[ $BASHPID != $$ ]] && exec true; echo AFTER'
BEFORE
# expected: "BEFORE" then "AFTER"; actual: "AFTER" missing — exec replaced the shell

---

/bin/bash -c 'echo BEFORE; [[ $BASHPID != $$ ]] && exec true; echo AFTER'

---

echo BEFORE; find . -maxdepth 1 >/dev/null; echo AFTER
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • I am using the latest version of Claude Code

What's Wrong?

In native builds, the per-session shell snapshot injects wrapper functions that shadow grep and find (redirecting them to the embedded ugrep/bfs). Each function decides how to invoke the embedded tool with [[ $BASHPID != $$ ]]:

function find {
  ...
  if [[ -n $ZSH_VERSION ]]; then
    ARGV0=bfs "$_cc_bin" -S dfs -regextype findutils-default "$@"
  elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "win32" ]]; then
    ARGV0=bfs "$_cc_bin" ... "$@"
  elif [[ $BASHPID != $$ ]]; then          # $BASHPID is empty on bash < 4.0
    exec -a bfs "$_cc_bin" ... "$@"          #   => always taken => replaces the shell
  else
    (exec -a bfs "$_cc_bin" ... "$@")        #   correct branch, never reached
  fi
}

$BASHPID was introduced in bash 4.0. Under bash 3.2 it expands to the empty string, so [[ "" != "<pid>" ]] is always true and the bare exec branch runs even in the main shell. exec replaces the calling shell, so every command sequenced after grep/find in the same shell (; …, && …, brace groups) is silently dropped. Both injected functions are affected.

This is not a fringe configuration. /bin/bash on macOS has been frozen at 3.2.57 since 2007 (Apple ships no GPLv3 software) and is the system bash on every current Mac. Any user whose $SHELL is /bin/bash runs the Bash tool under bash 3.2 — common on machines set up before the zsh default, in CI, or by preference.

What Should Happen?

Commands sequenced after grep/find should run normally; the wrapper must never replace the user's main shell.

1. Fix the guard by defaulting $BASHPID to $$, so it collapses to the safe subshell branch when $BASHPID is unavailable and preserves the bare-exec optimization on bash 4.0+:

elif [[ ${BASHPID:-$$} != $$ ]]; then

On bash < 4.0 the comparison is false and the always-correct ( exec … ) branch runs; on 4.0+ behavior is unchanged. Apply to every wrapper using this guard (grep, find, and any future ones).

2. Provide a supported opt-out for the shadowing. The Bash tool monkey-patches the user's shell with functions that shadow grep/find; this is invasive, depends on shell internals (this bug is one failure mode), and currently can't be turned off. The earlier builtin-ripgrep path honored USE_BUILTIN_RIPGREP=0, but a similar escape hatch was never added for the embedded bfs/ugrep functions. A documented env var (a USE_BUILTIN_RIPGREP-style flag, or CLAUDE_CODE_DISABLE_EMBEDDED_SEARCH=1) and/or settings key that suppresses these functions and falls back to system grep/find (and rg when present) would:

  • Make failures like this debuggable — isolating it would be a one-line experiment (disable shadowing → the silent truncation disappears → cause confirmed) and provide an immediate workaround while a fix ships. As things stand, this problem was EXTREMELY confusing to track down!
  • Respect users who prefer their own toolchain.
  • Bound the blast radius of any future shell-shadowing bug to a supported switch rather than a required release.

Error Messages/Logs

None — and that is central to the severity. The command succeeds (exit 0) and produces correct output; the shell is simply gone afterward, with no error, warning, or nonzero status. Observed vs. expected:

$ /bin/bash -c 'echo BEFORE; [[ $BASHPID != $$ ]] && exec true; echo AFTER'
BEFORE
# expected: "BEFORE" then "AFTER"; actual: "AFTER" missing — exec replaced the shell

Steps to Reproduce

  1. On macOS, ensure $SHELL=/bin/bash (system bash 3.2.57). Minimal check, guard in isolation:
    /bin/bash -c 'echo BEFORE; [[ $BASHPID != $$ ]] && exec true; echo AFTER'
    → prints only BEFORE.
  2. In Claude Code (native build, ≥ ~2.1.117) with that shell, run any Bash tool call that sequences work after find/grep:
    echo BEFORE; find . -maxdepth 1 >/dev/null; echo AFTER
    → prints only BEFORE; AFTER (and anything after find) silently does not run.

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

2.1.116

Claude Code Version

2.1.150 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

This report was drafted with Claude's help. This appears to be standard practice in this repository, but I wanted to call it out because I do not normally use LLMs in my written communication.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING