BASH_FUNC_grep / BASH_FUNC_find wrappers break under `set -u` (unguarded $ZSH_VERSION)

Resolved 💬 3 comments Opened Apr 28, 2026 by heymumford Closed May 1, 2026

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:

``bash
#!/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; }
``

  1. `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.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗