claude-code - 💡(How to fix) Fix Exported grep/find shell wrappers reference $ZSH_VERSION unguarded → fails under set -u

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…

Claude Code exports bash function wrappers for grep and find (visible via declare -F) that reference $ZSH_VERSION without a ${ZSH_VERSION-} default-empty guard. When the function is invoked from a bash subshell running under set -u (or set -euo pipefail), the unbound variable trips a non-fatal error message:

sh: line 6: ZSH_VERSION: unbound variable

The exit code is preserved (the wrappers fall through to the real binaries), so it's cosmetic — but it pollutes any just/make/CI recipe that runs strict-mode bash, which is most non-trivial automation.

Error Message

Claude Code exports bash function wrappers for grep and find (visible via declare -F) that reference $ZSH_VERSION without a ${ZSH_VERSION-} default-empty guard. When the function is invoked from a bash subshell running under set -u (or set -euo pipefail), the unbound variable trips a non-fatal error message:

Root Cause

Claude Code exports bash function wrappers for grep and find (visible via declare -F) that reference $ZSH_VERSION without a ${ZSH_VERSION-} default-empty guard. When the function is invoked from a bash subshell running under set -u (or set -euo pipefail), the unbound variable trips a non-fatal error message:

sh: line 6: ZSH_VERSION: unbound variable

The exit code is preserved (the wrappers fall through to the real binaries), so it's cosmetic — but it pollutes any just/make/CI recipe that runs strict-mode bash, which is most non-trivial automation.

Fix Action

Workaround

Repos can defend by unset -f grep find 2>/dev/null || true at the top of any strict-mode shell scope. We've applied this in scripts/install-hooks pre-push and a just recipe in our automation-factory codebase. Not durable — every new strict-mode entry point needs the same incantation.

Code Example

sh: line 6: ZSH_VERSION: unbound variable

---

$ declare -f grep | head -10
grep ()
{
    local _cc_bin="${CLAUDE_CODE_EXECPATH:-}";
    [[ -x $_cc_bin ]] || _cc_bin=/Users/$USER/.local/bin/claude;
    if [[ ! -x $_cc_bin ]]; then
        command grep "$@";
        return;
    fi;
    if [[ -n $ZSH_VERSION ]]; then
                ^^^^^^^^^^^
                # ← unguarded; trips set -u
        ARGV0=ugrep "$_cc_bin" -G ...

---

$ bash -c 'set -euo pipefail; grep --version > /dev/null'
sh: line 6: ZSH_VERSION: unbound variable

---

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

Summary

Claude Code exports bash function wrappers for grep and find (visible via declare -F) that reference $ZSH_VERSION without a ${ZSH_VERSION-} default-empty guard. When the function is invoked from a bash subshell running under set -u (or set -euo pipefail), the unbound variable trips a non-fatal error message:

sh: line 6: ZSH_VERSION: unbound variable

The exit code is preserved (the wrappers fall through to the real binaries), so it's cosmetic — but it pollutes any just/make/CI recipe that runs strict-mode bash, which is most non-trivial automation.

Repro

On macOS with Claude Code installed and an interactive bash session inheriting the wrappers:

$ declare -f grep | head -10
grep ()
{
    local _cc_bin="${CLAUDE_CODE_EXECPATH:-}";
    [[ -x $_cc_bin ]] || _cc_bin=/Users/$USER/.local/bin/claude;
    if [[ ! -x $_cc_bin ]]; then
        command grep "$@";
        return;
    fi;
    if [[ -n $ZSH_VERSION ]]; then
                ^^^^^^^^^^^
                # ← unguarded; trips set -u
        ARGV0=ugrep "$_cc_bin" -G ...

Repro under strict mode:

$ bash -c 'set -euo pipefail; grep --version > /dev/null'
sh: line 6: ZSH_VERSION: unbound variable

find has the identical pattern.

Expected

Wrappers should guard $ZSH_VERSION references with ${ZSH_VERSION-} so they expand to empty when unset:

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

This is the canonical bash idiom for shell-detection probes (also recommended by ShellCheck SC2154 mitigation).

Workaround

Repos can defend by unset -f grep find 2>/dev/null || true at the top of any strict-mode shell scope. We've applied this in scripts/install-hooks pre-push and a just recipe in our automation-factory codebase. Not durable — every new strict-mode entry point needs the same incantation.

Environment

  • Claude Code installed via ~/.local/bin/claude
  • macOS (Darwin), bash 5.x via Homebrew
  • Surfaced from a just recipe pipeline running under set -euo pipefail

Suggested fix

Two-character change per wrapper. The same correction applies to any other Claude Code-injected wrapper that probes $ZSH_VERSION or $BASH_VERSION for shell detection.

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