Claude Codeのzshでloginが走り固まる原因とBashツールの回避策
ターミナルでは動くのにBashツールだけがlsでも「Running…」のまま戻らない。zshのシェルスナップショットにloginが裸で書かれる報告の仕組みと、切り分け手順、CLAUDE_CODE_SHELLでの回避策を示します。
Claude CodeのBashツールが、lsのような一瞬で終わるコマンドでも「Running…」のまま戻らない。同じコマンドを普通のターミナルで打てば即座に終わる。この症状について、macOSのzsh環境でシェルスナップショットにloginという行が裸で書かれ、/usr/bin/loginが実行されて入力待ちで止まる、という報告がGitHubのissue #99254に上がっています。
回避策は~/.claude/settings.jsonのenvでCLAUDE_CODE_SHELLを/bin/bashにすることです。ただし、zshのエイリアスや関数がBashツールから消えるという代償があります。切り分けから回避策の副作用までを順に見ます。
症状: すべてのBashコマンドが「Running…」で止まる
issueの報告者の環境は、Claude Code 2.1.288と2.1.285(どちらもネイティブインストール、クリーン再インストール済み)、macOSのdarwin-arm64、zsh 5.9です。症状は次の2点です。
- Bashツールの呼び出しが、
lsでもmake -kでも終わらない - 同じコマンドをターミナルで直接実行すると、すぐ終わる
「コマンドが重いだけ」ではなく、中身に関係なく全部が止まるのが特徴です。Bashツールのコマンドが特定の1本だけ失敗するなら、別の原因を疑うほうが近道です。
なぜloginが走るのか: スナップショットにloginが裸で残る
Claude CodeのBashツールは、セッション開始時に~/.zshrc・~/.bashrc・~/.profileのいずれかを読み込み、得られたエイリアス・関数・シェルオプションを以降のBashコマンドすべてに適用します。この書き出し先が~/.claude/shell-snapshots/のスナップショットファイルです。各コマンドはこれをsourceしてから実行されます。起動ファイルの扱いはBashツールとシェル起動設定の解説にまとめています。
報告者の診断はこうです。
- 止まっている間にプロセスを見ると、ラッパーの
/bin/zsh -c source ~/.claude/shell-snapshots/snapshot-zsh-….sh …の子としてloginが1つ生きている - スナップショットを手で
sourceすると、command not found: promptsubst、command not found: run-help=manのようなエラーが出る - 本来
setopt promptsubstやalias run-help=manであるべき行が、接頭辞なしの名前だけで書かれている - ログインシェルで有効になるzshのオプション
loginも同じく裸のlogin行になり、loginコマンドとして/usr/bin/loginが起動する - ユーザーのコマンドには
< /dev/nullが付くが、sourceの段階には付かないので、loginが標準入力を待ち続ける
報告の筋書きは、スナップショットを作るシェルがログインシェルで、そのオプション一覧が裸の名前のまま書き出される、というものです。
手元のzshで確かめられること
裸の名前が書かれる理屈は、Claude Codeを起動しなくても手元のzshで見られます。setoptは有効なオプション名だけを1行ずつ出し、aliasは名前=値の形で出します。これをそのままファイルに書いてsourceすれば、各行は「コマンド」として解釈されます。次はzsh 5.9(macOS、ホームの設定を読まない空のZDOTDIR)での出力です。
D=$(mktemp -d)
# ログインシェル(-l)の setopt
env -i PATH=/usr/bin:/bin ZDOTDIR=$D zsh -l -c 'setopt'
# 非ログインシェルの setopt
env -i PATH=/usr/bin:/bin ZDOTDIR=$D zsh -c 'setopt'
# ログインシェルの alias
env -i PATH=/usr/bin:/bin ZDOTDIR=$D zsh -l -c 'alias'出力は上から順に、次のとおりです。
nohashdirs
loginnohashdirsrun-help=man
which-command=whenceログインシェル(-l)のsetoptだけにloginが出ます。aliasの出力は、issueのエラーに出てくるrun-help=man・which-command=whenceと一致します。issueの報告では、これらが接頭辞なしで並んでいたことになります。
ここで確かめたのは「setoptとaliasの素の出力がこの形になる」ところまでです。スナップショットの組み立て方は公開されていないため、上の5点はissue報告者の診断です。
自分の環境で同じ症状かを切り分ける
Bashツールが固まったら、別のターミナルタブで次を順に試します。Claude Codeのセッション内からは実行できないので、必ず別のタブで行います。
# 1. login が子プロセスとして残っているか
ps -axo pid,ppid,command | grep -E 'login|shell-snapshots' | grep -v grep
# 2. スナップショットに裸の login 行があるか
grep -n -x 'login' ~/.claude/shell-snapshots/snapshot-zsh-*.sh
# 3. 標準入力を空にして source したときに login: で始まるエラーが出るか
zsh -c 'source ~/.claude/shell-snapshots/snapshot-zsh-XXXX.sh' < /dev/nullsnapshot-zsh-XXXX.shは、2で見つかった実際のファイル名に置き換えます。結果の読み方は次のとおりです。
| 結果 | 読み方 |
|---|---|
1でloginがzsh -c source …の子として見える | 読み方issueの症状と一致。2と3も確認する |
2でloginだけの行が見つかる | 読み方裸のオプション行が書かれている |
3でlogin: …command not found: …が出て、/dev/nullのおかげで即戻る | 読み方報告と同じ。< /dev/nullが無いと止まる |
| 1〜3のどれにも当てはまらない | 読み方別の原因。下の表を参照 |
issueの実測では、3は0.026秒で戻っています。標準入力を空にすると即座に戻るので、止まる理由は入力待ちだという報告の診断と整合します。
回避策: CLAUDE_CODE_SHELL=/bin/bashでBashツールのシェルを替える
報告者が使った回避策は、~/.claude/settings.jsonのenvでCLAUDE_CODE_SHELLを指定することです。
{
"env": {
"CLAUDE_CODE_SHELL": "/bin/bash"
}
}CLAUDE_CODE_SHELLは、Bashツールのコマンドを実行するシェルを指定する環境変数です。bashかzshのバイナリのパスを受け付けます。fishなど他のシェルは未対応で、有効なパスでなければ無視されて自動検出に戻ります。自動検出は、$SHELLがbashかzshを指していればそれを使い、そうでなければPATHと標準的なインストール先から最初に動くzsh、次にbashを選びます。
設定後はClaude Codeを再起動し、新しいセッションで確かめます。スナップショットはセッション開始時に作られるため、実行中のセッションには効きません。
どの設定ファイルに書くか
envは設定ファイルのスコープごとに適用範囲が変わります。回避策は自分のMacの問題なので、置き場所は次の順で考えます。
| 置き場所 | 効く範囲 | この回避策での扱い |
|---|---|---|
~/.claude/settings.json | 効く範囲自分の全プロジェクト | この回避策での扱い第一候補。報告者もここに書いている |
.claude/settings.local.json | 効く範囲自分のこのプロジェクトだけ | この回避策での扱い特定のリポジトリでだけ固まるとき、またはお試しのとき |
.claude/settings.json | 効く範囲フォルダを使う全員 | この回避策での扱い避ける。zshで問題の出ていない同僚のBashツールまでbashに替わる |
envに書いた値は、シェルでexportした同名の変数より優先されます。ターミナルでexport CLAUDE_CODE_SHELL=...を済ませていても、設定ファイル側の値が勝つ点は頭に置いておきます。
効いているかをBashツールの中から確かめる
再起動した新しいセッションで、Claudeに次の1行を実行させます。固まらずに返ってくれば、まず症状は避けられています。
echo "BASH_VERSION=${BASH_VERSION:-unset} ZSH_VERSION=${ZSH_VERSION:-unset}"CLAUDE_CODE_SHELLが効いていれば、実行しているのはbashなのでBASH_VERSIONに値が入り、ZSH_VERSIONはunsetになります。逆なら指定が無視され、自動検出でzshが選ばれています。パスの綴りや、そのパスに実行できるbashがあるかを見直します。
スナップショットが溜まっているときの見方
切り分けの2は、snapshot-zsh-*.shが複数あると全部に当たります。セッションのたびに別のファイルが作られるためです。直近のものだけを見るなら、更新時刻の新しい順に1つ選びます。
ls -t ~/.claude/shell-snapshots/snapshot-zsh-*.sh | head -1~/.claude/shell-snapshots/の古いファイルは、v2.1.117以降、cleanupPeriodDaysの保持期間に従って掃除の対象になっています。ただし、報告された原因は書き出される内容にあります。古いファイルの掃除では扱えません。
回避策の代償: zshの設定がBashツールから消える
シェルをbashに替えると、Bashツールが読み込む起動ファイルは~/.bashrc(なければ~/.profile)に変わります。~/.zshrcに書いたエイリアスや関数、PATHの調整は、Bashツールでは見えなくなります。
- Homebrewなどの
PATH設定が~/.zprofileや~/.zshrcにしか無い場合、Bashツールでbrewやnvm経由のコマンドが見つからなくなる - ツールが見つからないときは、必要な設定を
~/.bashrcにも書き足す。CLAUDE_ENV_FILEで環境変数を持ち込む方法もある(持ち越しの条件は上のシェル起動設定の記事を参照) - 症状が直ったら、
CLAUDE_CODE_SHELLの行を外して元に戻せる
回避策はあくまで「固まる経路を避ける」ためのもので、スナップショットの書き出し自体は直りません。なおCLAUDE_CODE_SHELLが切り替えるのはClaude CodeがBashツールに使うシェルだけで、普段のターミナルで使っているzshの設定や、ログインシェルの指定には影響しません。
タイムアウトの設定では出口にならない
Bashツールのコマンドには時間制限があります。フォアグラウンドで動くコマンドの既定は2分、上限は10分で、環境変数BASH_DEFAULT_TIMEOUT_MS(既定120000)とBASH_MAX_TIMEOUT_MS(既定600000)で変えられます。ビルドやテストのような長いコマンド向けの調整です。
今回の報告は、lsでさえ「Running…」のまま戻らないというものです。コマンドが長いのではなく、コマンドの手前のsourceの段階で入力待ちになっています。値を大きくしても待ち時間が延びるだけなので、BASH_DEFAULT_TIMEOUT_MSをいじる前に、loginが子プロセスに居ないかを先に見ます。
ほかの「Bashツールが動かない」との見分け方
Bashツールが止まる、あるいは失敗する原因は他にもあり、症状ごとに対処が違います。
| 症状 | 疑う原因 | 参照先 |
|---|---|---|
全コマンドが「Running…」で止まり、loginが子プロセスに見える | 疑う原因zshスナップショットのlogin行(本記事) | 参照先上の回避策 |
launchdやcronから動かしたclaude -pが出力0バイトで止まる | 疑う原因重い.zshrc、またはスナップショット作成待ち | 参照先launchdで固まる原因の解説 |
spawn E2BIGで失敗する | 疑う原因環境変数や引数の総量が上限を超えている | 参照先spawn E2BIGの原因 |
Windowsのlsやgrepが動かない | 疑う原因Git BashのPATHが壊れている | 参照先Git BashのPATH問題 |
| 使っているシェルがzsh・bash以外 | 疑う原因CLAUDE_CODE_SHELLが対応するのはbashとzshのみ | 参照先Nushellのときの通し方 |
launchdの記事にも#99254の簡単な紹介はありますが、そちらはclaude -pの夜間ジョブが主題です。本記事は、対話セッションのBashツールが全コマンドで止まる症状に絞っています。
issueの状況と、直る場合の見通し
issueは2026-10-03に起票され、ラベルはbug・has repro・platform:macos・area:bashです。報告者が示した修正案は2つあります。
- スナップショットに書くとき、各オプションの前に
setoptを、各エイリアスの前にaliasを付ける sourceの段階でも標準入力を/dev/nullから読ませる
回避策を入れたままだとzshの設定が使えない状態が続きます。修正が入ればchangelogに載るので、アップデートのたびに該当項目を探し、直ったらCLAUDE_CODE_SHELLの行を外して挙動を戻す運用になります。
参考までに、2.1.51のchangelogには、スナップショットがあるときBashツールが-l(ログインシェル)を既定で使わなくなったとあります。以前はCLAUDE_BASH_NO_LOGIN=trueが必要だった動作です。
同じ症状に当たったときは、issueにコメントとして足せる情報があります。報告者が書いたのは、Claude Codeのバージョン、macOSとzshのバージョン、psで見たloginの親子関係、スナップショットを手でsourceしたときのエラー行(例では225・234・236行目)です。自分の環境の値が揃えば、再現条件の絞り込みに役立ちます。報告者が使っていたのはzsh 5.9で、起動ファイルの内容や.zshrcの有無による違いは報告に書かれていません。
まとめ
lsですら固まるBashツールで、別タブから見てloginが子プロセスに残っていたら、zshスナップショットのlogin行を疑う価値があります。報告された原因は、スナップショットの裸のlogin行と、source時に標準入力が閉じられていないことで、根本の修正はissue側の対応待ちです。当面はCLAUDE_CODE_SHELL=/bin/bashで避け、~/.zshrcにしか無いPATHや関数を~/.bashrcへ足すかどうかを決めておくと、回避策の副作用で別の失敗を呼ばずに済みます。