claude-code - 💡(How to fix) Fix BASH_FUNC_grep / BASH_FUNC_find wrappers break under `set -u` (unguarded $ZSH_VERSION) [1 comments, 2 participants]

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…
GitHub stats
anthropics/claude-code#54124Fetched 2026-04-28 06:38:36
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
0
Timeline (top)
labeled ×5commented ×1

The grep and find bash function wrappers Claude Code injects via the shell-snapshot mechanism reference $ZSH_VERSION without the ${ZSH_VERSION:-} default-value guard. Any subprocess that runs set -u (e.g. a strict git hook) and pipes through grep/find triggers an unbound variable error, which pipefail propagates as pipe failure. Real-world impact: strict commit-msg hooks reject every commit with a confusing stderr trail, even when the message is well-formed.

Error Message

The grep and find bash function wrappers Claude Code injects via the shell-snapshot mechanism reference $ZSH_VERSION without the ${ZSH_VERSION:-} default-value guard. Any subprocess that runs set -u (e.g. a strict git hook) and pipes through grep/find triggers an unbound variable error, which pipefail propagates as pipe failure. Real-world impact: strict commit-msg hooks reject every commit with a confusing stderr trail, even when the message is well-formed.

Root Cause

In the wrapper template (bundled JS function, minified name uJ8, in the claude Mach-O binary). The emitted bash function looks like this — line 5 is the bug:

function grep {
  local _cc_bin="${CLAUDE_CODE_EXECPATH:-}"     # correctly guarded
  [[ -x $_cc_bin ]] || _cc_bin=$(command -v claude 2>/dev/null)
  if [[ ! -x $_cc_bin ]]; then command grep "$@"; return; fi
  if [[ -n $ZSH_VERSION ]]; then                # ← unguarded — errors under set -u
    ARGV0=ugrep "$_cc_bin" -G --ignore-files --hidden -I --exclude-dir=.git ... "$@"
  elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "win32" ]]; then
    ARGV0=ugrep "$_cc_bin" -G ... "$@"
  elif [[ $BASHPID != $$ ]]; then
    exec -a ugrep "$_cc_bin" -G ... "$@"
  else
    (exec -a ugrep "$_cc_bin" -G ... "$@")
  fi
}

The very next line above uses ${VAR:-} correctly, so the idiom is already known to the author of this template — this looks like an oversight on a single line. The same template generates the find wrapper (BASH_FUNC_find%%) and has the same bug.

Fix Action

Fix

In the JS template, change:

  if [[ -n $ZSH_VERSION ]]; then

to:

  if [[ -n ${ZSH_VERSION:-} ]]; then

Code Example

#!/usr/bin/env bash
   set -euo pipefail
   COMMIT_MSG=$(cat "$1")
   echo "$COMMIT_MSG" | grep -qE '[A-Z]{2,}-[0-9]+' || { echo "missing ticket ref"; exit 1; }

---

function grep {
  local _cc_bin="${CLAUDE_CODE_EXECPATH:-}"     # correctly guarded
  [[ -x $_cc_bin ]] || _cc_bin=$(command -v claude 2>/dev/null)
  if [[ ! -x $_cc_bin ]]; then command grep "$@"; return; fi
  if [[ -n $ZSH_VERSION ]]; then                # ← unguarded — errors under set -u
    ARGV0=ugrep "$_cc_bin" -G --ignore-files --hidden -I --exclude-dir=.git ... "$@"
  elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "win32" ]]; then
    ARGV0=ugrep "$_cc_bin" -G ... "$@"
  elif [[ $BASHPID != $$ ]]; then
    exec -a ugrep "$_cc_bin" -G ... "$@"
  else
    (exec -a ugrep "$_cc_bin" -G ... "$@")
  fi
}

---

if [[ -n $ZSH_VERSION ]]; then

---

if [[ -n ${ZSH_VERSION:-} ]]; then
RAW_BUFFERClick to expand / collapse

Summary

The grep and find bash function wrappers Claude Code injects via the shell-snapshot mechanism reference $ZSH_VERSION without the ${ZSH_VERSION:-} default-value guard. Any subprocess that runs set -u (e.g. a strict git hook) and pipes through grep/find triggers an unbound variable error, which pipefail propagates as pipe failure. Real-world impact: strict commit-msg hooks reject every commit with a confusing stderr trail, even when the message is well-formed.

Reproduction

  1. In a Claude Code session, confirm wrapper presence: declare -f grep

  2. Create a commit-msg hook with the standard strict prelude:

    #!/usr/bin/env bash
    set -euo pipefail
    COMMIT_MSG=$(cat "$1")
    echo "$COMMIT_MSG" | grep -qE '[A-Z]{2,}-[0-9]+' || { echo "missing ticket ref"; exit 1; }
  3. `git commit -m "fix: foo

    ABC-123"`

Expected: hook passes (message contains ABC-123).

Actual: hook fails with environment: line 6: ZSH_VERSION: unbound variable on every grep invocation. set -u + pipefail cause the pipe to return non-zero before grep ever sees stdin, so the -qE check is never actually evaluated against the message.

Root cause

In the wrapper template (bundled JS function, minified name uJ8, in the claude Mach-O binary). The emitted bash function looks like this — line 5 is the bug:

function grep {
  local _cc_bin="${CLAUDE_CODE_EXECPATH:-}"     # correctly guarded
  [[ -x $_cc_bin ]] || _cc_bin=$(command -v claude 2>/dev/null)
  if [[ ! -x $_cc_bin ]]; then command grep "$@"; return; fi
  if [[ -n $ZSH_VERSION ]]; then                # ← unguarded — errors under set -u
    ARGV0=ugrep "$_cc_bin" -G --ignore-files --hidden -I --exclude-dir=.git ... "$@"
  elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "win32" ]]; then
    ARGV0=ugrep "$_cc_bin" -G ... "$@"
  elif [[ $BASHPID != $$ ]]; then
    exec -a ugrep "$_cc_bin" -G ... "$@"
  else
    (exec -a ugrep "$_cc_bin" -G ... "$@")
  fi
}

The very next line above uses ${VAR:-} correctly, so the idiom is already known to the author of this template — this looks like an oversight on a single line. The same template generates the find wrapper (BASH_FUNC_find%%) and has the same bug.

Fix

In the JS template, change:

  if [[ -n $ZSH_VERSION ]]; then

to:

  if [[ -n ${ZSH_VERSION:-} ]]; then

Workarounds (not a real fix)

  • Per-invocation: ZSH_VERSION='' git commit ...
  • Shell-rc export ZSH_VERSION='' — rejected, masks the real bug and pollutes the env namespace.

Environment

  • Claude Code: 2.1.121 (also reproduces on 2.1.1182.1.120 — same template bytes).
  • macOS / arm64 / bash 5 (/usr/local/bin/bash).
  • Wrapper bytes verified via declare -f grep and env | grep BASH_FUNC_.
  • Buggy literal if [[ -n $ZSH_VERSION ]]; then appears 3× in the binary (deduplicated string constants + main/worker copies); one logical bug, one logical fix.

extent analysis

TL;DR

The issue can be fixed by modifying the grep function wrapper to use the default-value guard ${ZSH_VERSION:-}.

Guidance

  • The root cause of the issue is the missing default-value guard in the grep function wrapper, which causes an unbound variable error when set -u is enabled.
  • To verify the issue, create a commit-msg hook with set -euo pipefail and pipe the commit message through grep.
  • To fix the issue, update the grep function wrapper to use ${ZSH_VERSION:-} instead of $ZSH_VERSION.
  • The same fix should be applied to the find wrapper, which has the same bug.

Example

The corrected grep function wrapper should look like this:

if [[ -n ${ZSH_VERSION:-} ]]; then

This change ensures that the ZSH_VERSION variable is checked with a default value, preventing the unbound variable error.

Notes

The fix is specific to the grep and find function wrappers injected by Claude Code. The issue is not related to the git or bash versions, but rather the specific implementation of the wrappers.

Recommendation

Apply the workaround by modifying the grep and find function wrappers to use the default-value guard ${ZSH_VERSION:-}. This fix addresses the root cause of the issue and prevents the unbound variable error.

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

claude-code - 💡(How to fix) Fix BASH_FUNC_grep / BASH_FUNC_find wrappers break under `set -u` (unguarded $ZSH_VERSION) [1 comments, 2 participants]