Claude Media
Claude Codeのzshでloginが走り固まる原因とBashツールの回避策

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ツールとシェル起動設定の解説にまとめています。

報告者の診断はこうです。

  1. 止まっている間にプロセスを見ると、ラッパーの/bin/zsh -c source ~/.claude/shell-snapshots/snapshot-zsh-….sh …の子としてloginが1つ生きている
  2. スナップショットを手でsourceすると、command not found: promptsubst、command not found: run-help=manのようなエラーが出る
  3. 本来setopt promptsubstやalias run-help=manであるべき行が、接頭辞なしの名前だけで書かれている
  4. ログインシェルで有効になるzshのオプションloginも同じく裸のlogin行になり、loginコマンドとして/usr/bin/loginが起動する
  5. ユーザーのコマンドには< /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
login
nohashdirs
run-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/null

snapshot-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へ足すかどうかを決めておくと、回避策の副作用で別の失敗を呼ばずに済みます。

この記事を共有:XはてブLinkedIn