Mac の Claude Code で Bash ツールが zsh として動く問題は CLAUDE_CODE_SHELL で直る

未分類

Mac で Claude Code を使うと、Bash ツールのコマンドは bash ではなく zsh で実行される。 Claude は bash の書き方でコマンドを組み立てるので、zsh との文法の違いで途中から実行されないことがある。 環境変数 CLAUDE_CODE_SHELL に /bin/bash を指定すると、Bash ツールは bash で動くようになる。 この記事では、Mac の直近2か月のセッションログで実際に何件失敗していたかを数え、指定の前後で挙動がどう変わるかを実機で確かめた結果をまとめる。

Bash ツールが zsh で動く理由

Claude Code の Bash ツールは、名前に反して「利用者のシェル」でコマンドを実行する。 公式ドキュメントによれば、シェルの自動判定は $SHELL が bash か zsh を指していればそれを使う。 macOS は Catalina 以降、ログインシェルの既定が zsh なので、何も設定しなければ Bash ツールは zsh で動く。

Windows では事情が違う。 Windows 版は Git Bash を使うので、同じ設定のまま2台で使っていても、文法の違いで失敗するのは Mac 側だけになる。

筆者はこの差を埋めるために、Bash ツールに渡る bash の書き方を zsh 向けに書き換えるフックを試していた。 しかし、書き換えが必要な構文の種類が多く、うまく動かなかった。 シェルの側を bash に合わせるほうが筋がよい。

直近2か月のログで数えた zsh 固有のエラー

Mac の ~/.claude/projects/ に残っているセッションログ(2026年8月から9月)を対象に、Bash ツールの結果から zsh でしか起きないエラーを数えた。 期間中の Bash ツールの呼び出しは約9,200回あった。 そのうち zsh 固有のエラーは77件で、約50のセッションに散らばっていた。

エラーの種類件数実際に失敗したコマンドの例bash での結果
glob が何にも一致しない(no matches found)62grep -rn foo . --include=*.htmlそのまま実行される
= で始まる語をコマンド名として展開する(=== not found)11echo ========== と表示される
変数のワード分割がされない2FILES="a.md b.md"; for f in $FILES2要素でループする
status が読み取り専用1status=$(grep ...)普通に代入できる
ループ変数 path で PATH が壊れる1for path in "/" "/lessons"; do ...何も起きない

件数は、1回のコマンド実行を1件として数えている。 glob の不一致62件のうち14件は、grep の --include=*.html のようにクォートしていないオプションが glob として展開されたものだった。 bash なら一致するファイルがなければ文字列がそのまま grep に渡り、意図どおりに動く。

8月が72件、9月が5件と、9月は件数が少ない。 ただし9月は Mac で Claude Code を使う時間が減っており(Bash ツールの呼び出しが8月の約8,800回に対して9月は約440回)、呼び出しあたりの頻度は下がっていない。 最後に起きたのは9月25日で、今も続いている。

失敗の仕方が分かりにくい

件数より困るのは、失敗の現れ方である。

zsh は glob の不一致や = の展開エラーを起こすと、そのコマンド行の残りを実行せずに終了する。 cat a.mjs; echo =====; cat b.mjs のように区切り線を挟んで複数のファイルを読もうとすると、最初のファイルだけ表示して残りが消える。

ループ変数に path を使った例では、zsh の小文字の path が PATH と連動した配列なので、ループに入った時点で PATH が書き換わる。 その後の tr や head が command not found になり、コマンドを直しに行くと原因が見えにくい。

ワード分割の例は、エラーが出ないまま終わることもある。 2026年8月22日のセッションでは、for f in $FILES が1要素でしか回らず、sed が File name too long で失敗した。 同じセッションには、終了コードが0のまま何も変更されずに終わる経路もあった。

失敗するたびに、原因の調べ直しと再実行が挟まることになる。

CLAUDE_CODE_SHELL で bash に切り替わるか

公式ドキュメントの環境変数一覧には、CLAUDE_CODE_SHELL が次のように書かれている。

  • Bash ツールのコマンドを実行するシェルを指定する
  • 指定できるのは bash か zsh の実行ファイルのパスだけで、fish などは使えない
  • 使えないパスを指定すると、何も言わずに自動判定へ戻る

この変数は Claude Code 2.0.65 で追加された。 公式の説明は分かったので、Mac(Claude Code 2.1.282)で claude -p を使い、同じコマンドを未設定のときと /bin/bash を指定したときで実行して比べた。

確認したこと未設定(zsh)CLAUDE_CODE_SHELL=/bin/bash
実際のシェルZSH_VERSION=5.9BASH_VERSION=3.2.57(1)-release
echo ======= not found で行の残りが止まる==== と表示される
status=okread-only variable: statusstatus=ok
for f in $FILES(2ファイル)[a.md b.md] の1回だけ[a.md] [b.md] の2回
for path in x y の後の command -v head見つからない/usr/bin/head
grep ... --include=*.nomatchextno matches found で行全体が止まるgrep が実行され、後続の echo も動く
brew、node、pnpm、gh、codex、claude の場所見つかる同じ場所で見つかる

指定すると、表の zsh 固有のエラーはすべて起きなくなった。 settings.json の env に書いた場合も、--settings '{"env":{"CLAUDE_CODE_SHELL":"/bin/bash"}}' で起動して BASH_VERSION=3.2.57 になることを確かめた。

設定方法

~/.claude/settings.json の env に1行足す。

{
  "env": {
    "CLAUDE_CODE_SHELL": "/bin/bash"
  }
}

~/.zshrc に export CLAUDE_CODE_SHELL=/bin/bash を書いてもよいが、settings.json なら Claude Code をどのターミナルから起動しても効く。 設定リファレンスの「env で無視される変数」に CLAUDE_CODE_SHELL は入っていない。

設定したら、Claude Code のセッションを開き直す。 動作中のセッションで切り替わるかどうかは確かめていない。

settings.json を Windows と共有していても問題はない。 Windows で CLAUDE_CODE_SHELL を未設定、/bin/bash、存在しないパスの3通りにして同じ確認をしたところ、どれも Git Bash 5.2.37 で動いた。 Windows では /bin/bash が使えないパスとして扱われ、自動判定に戻るためと考えられる。

切り替える前に確かめておくこと

macOS の /bin/bash は 3.2

macOS に標準で入っている bash は 3.2 で、連想配列(declare -A)、mapfile、${var,,} のような bash 4 以降の構文は使えない。 公式ドキュメントの例が Homebrew の bash(/opt/homebrew/bin/bash)を指定しているのはこのためだろう。

筆者の環境では、2か月分のログに declare -A、mapfile、readarray、${var,,} を使ったコマンドは1件もなかった。 このため /bin/bash で足りると判断した。 bash 4 以降の構文を使うスクリプトが多いなら、brew install bash で入れた bash を指定すればよい。

.zshrc のエイリアスと関数は読まれなくなる

Claude Code はセッションの開始時に、シェルに応じて ~/.zshrc、~/.bashrc、~/.profile のどれかを読み込み、エイリアスと関数を Bash ツールに引き継ぐ。 bash に切り替えると、~/.zshrc に書いたエイリアスや関数は Bash ツールから使えなくなる。

PATH は別で、Claude Code を起動したシェルの環境をそのまま引き継ぐ。 筆者の Mac は Homebrew の PATH を ~/.zprofile で、~/.local/bin を ~/.zshrc で通しているが、bash に切り替えても brew や claude は同じ場所で見つかった。

bash にしても macOS のコマンドの違いは残る

bash に変えて直るのは、シェルの文法の違いだけである。 macOS の sed -i の書き方、timeout コマンドが無いこと、stat や date のオプションの違いは、BSD 系のコマンドと GNU 系のコマンドの違いなので、シェルを変えても残る。 ログにも command not found: timeout がまとまった数あったが、これは bash にしても解消しない。

まとめ

  • Mac の Claude Code の Bash ツールは zsh で動き、直近2か月で約9,200回の呼び出しのうち77件が zsh 固有のエラーで失敗していた
  • zsh のエラーはコマンド行の残りを止めたり、PATH を壊したり、成功したように見えたりするので、原因に気づきにくい
  • ~/.claude/settings.json の env に "CLAUDE_CODE_SHELL": "/bin/bash" を書けば、表の失敗はすべて起きなくなる。Windows と共有しても支障はない
  • 切り替えると ~/.zshrc のエイリアスと関数は Bash ツールで使えなくなり、/bin/bash は 3.2 のままである
#Claude Code#zsh#bash#macOS#シェル