Claude Codeのsandboxがzshで126になる原因と回避策
Linuxでログインシェルがzshのとき、sandbox有効だと全コマンドが出力ゼロのExit code 126で失敗する報告があります。仕組みと切り分け、SHELL=/bin/bashによる回避を示します。
Linuxでログインシェルがzshの環境でsandbox.enabledをtrueにすると、Bashツールのコマンドが種類を問わずExit code 126で失敗する報告があります。標準出力も標準エラーも空で、trueのようなシェル組み込みでも同じです。SHELL=/bin/bash claudeで起動すると、同じ環境でサンドボックスが正常に動いたと報告されています。
この記事は、anthropics/claude-codeのissue #104912(2026-10-24起票、open)の内容と、手元で確かめられる切り分けの手順をまとめたものです。
症状: 全コマンドが1秒余りでExit code 126になる
issueが挙げる症状は次の4点です。
- Bashツールのコマンドがすべて
Exit code 126で終わる。trueやtype echoも同じ - 標準出力と標準エラーが両方とも0バイト
- 失敗までの時間は約1.3秒
- 対話モードでも
-pでも、新しいセッションでも、permissions.denyを空にしても変わらない
--debugのログに出るのは、次の2行だけだと報告されています。
[Stall] tool_dispatch_end tool=Bash outcome=error durationMs=1302
Bash tool error (1302ms): code=tool_shell_error type=ShellErrorエラーメッセージに原因を示す語がありません。報告者は、原因の特定にstraceが必要だったと書いています。
なぜ126になるのか: 内側スクリプトのexec 3<&-
報告者がstraceで追った結果、サンドボックスの内側で走るスクリプトは次の形をしています。
exec 3<&- || exit 126; source ~/.claude/shell-snapshots/snapshot-zsh-*.sh … && eval <command> …先頭のexec 3<&-は、ファイルディスクリプタ3を閉じる操作です。報告者の見立てでは、すでに閉じているディスクリプタをもう一度閉じたとき、bashは成功扱いにしますが、zshは失敗扱いにします。失敗すると|| exit 126が働き、sourceもevalも実行されないまま終了します。コマンドの中身には一切届かないので、どんなコマンドでも同じ結果になります。
報告者が示した再現は次の2行です。
$ zsh -c 'exec 3<&-; exec 3<&- || exit 126; echo SURVIVED' # 出力なしで終了
$ bash -c 'exec 3<&-; exec 3<&- || exit 126; echo SURVIVED' # SURVIVEDここで分かるのは、126が「実行権限が無い」という一般的な意味の126ではなく、スクリプトが自分で選んだ終了コードだということです。コマンドが見つからない場合や権限が足りない場合を疑ってchmodやPATHを調べても、空振りに終わります。
手元のmacOSでは再現しなかった
同じ2行を、macOSのzsh 5.9(arm64-apple-darwin25.0)で試しました。結果はこうです。
zsh --version
zsh -c 'exec 3<&-; exec 3<&- || exit 126; echo SURVIVED'; echo "exit=$?"出力はSURVIVEDとexit=0で、zshでも2回目のexec 3<&-は成功しました。つまり「zshなら必ず失敗する」とは言えません。失敗するかどうかは、zshのバージョンやビルド、実行時のディスクリプタの状態によって変わる可能性があります。issueが扱う環境はLinuxなので、macOSの結果はそのまま当てはまらない点にも注意が必要です。
自分の環境が該当するかは、この2行を実機で流せば分かります。SURVIVEDが出なければ、issueの原因と同じ型です。出るなら、別の原因を疑います。
自分の環境で切り分ける
Claude Codeのセッション内では全コマンドが落ちるため、切り分けは別のターミナルで行います。
# 1. ログインシェルとSHELLの値
echo "$SHELL"
getent passwd "$USER" | cut -d: -f7
# 2. zshで内側の閉じ方が失敗するか(issueの再現)
zsh -c 'exec 3<&-; exec 3<&- || exit 126; echo SURVIVED'; echo "exit=$?"
# 3. ホストのbwrap自体は健全か
bwrap --new-session --die-with-parent --unshare-net --unshare-pid \
--unshare-user --cap-drop ALL --ro-bind / / --dev /dev --proc /proc \
--tmpfs /tmp -- echo strict-ok結果の読み方は次のとおりです。
| 1の結果 | 2の結果 | 3の結果 | 読み方 |
|---|---|---|---|
| zsh | 2の結果SURVIVEDが出ない | 3の結果strict-ok | 読み方issueと同じ型。次の回避策が有効な可能性が高い |
| zsh | 2の結果SURVIVEDが出る | 3の結果strict-ok | 読み方別の原因。シェル以外を疑う |
| zsh | 2の結果どちらでも | 3の結果エラー | 読み方ホストのbubblewrapが動いていない。後述の「bubblewrapとsocatが入っているか」へ |
| bash | 2の結果どちらでも | 3の結果strict-ok | 読み方issueの対象外。別の原因 |
3のbwrapコマンドは、報告者がホストのサンドボックス機能を確かめるために使ったものです。strict-okが出れば、ユーザー名前空間などのカーネル側の条件は満たされています。
自分のバージョンが回帰の範囲に入っているか
報告では、2.1.291までは動き、2.1.302以降で失敗します。手元のバージョンは次の1行で分かります。
claude --version2.1.291以前なら、126の原因は別にある可能性が高くなります。2.1.302以降で、ログインシェルがzshのLinuxなら、このissueの症状と重なります。2.1.292から2.1.301の間は、報告者が試していないので、どちらとも言えません。
引数の長さ超過ではないことをstraceで確かめる
126の原因が、起動コマンドの引数が長すぎて実行できなかったことなのか、内側スクリプトの終了なのかを分けたいときは、straceでexecveの戻り値を見ます。報告者の手順は次のとおりです。
strace -f -e trace=execve -s 4000 claude --debug -p \
"use the Bash tool to run: echo alive" 2>&1 | grep execve報告では、bwrapの引数、socatの中継、apply-seccomp、内側のzsh、スナップショットの読み込みまで、すべてのexecveが= 0で成功していました。そのうえで126を返せる行がexec 3<&- || exit 126しか残らない、というのが原因を絞った根拠です。= 0以外の行が出る環境は、issueとは別の原因です。
回避策: SHELL=/bin/bashでClaude Codeを起動する
報告者が確かめた回避策は、1つだけです。
SHELL=/bin/bash claude報告によれば、これでサンドボックスが「基本的なコマンド、終了コードの伝搬、スクリプト実行、ツール類」まで正常に動いたそうです。同じissueは、この方法が#77466で「未検証の案」として出ていたものと同じだとも書いています。
CLAUDE_CODE_SHELLで固定する
毎回環境変数を付けるのが面倒なら、~/.claude/settings.jsonのenvに書く手があります。CLAUDE_CODE_SHELLは、Bashツールのコマンドを実行するシェルを指定する環境変数で、bashかzshのバイナリのパスを受け付けます。
{
"env": {
"CLAUDE_CODE_SHELL": "/bin/bash"
}
}env-varsのドキュメントによれば、値が使えるパスでなければ無視されて自動検出に戻り、自動検出は$SHELLがbashかzshを指していればそれを使います。この動作は、SHELL=/bin/bashで直る理由と整合します。ただし、CLAUDE_CODE_SHELLでこの不具合が直ることは、issueでは試されていません。設定後は新しいセッションを起動し、次の1行で効いているかを見てください。
echo "BASH_VERSION=${BASH_VERSION:-unset} ZSH_VERSION=${ZSH_VERSION:-unset}"BASH_VERSIONに値が入り、ZSH_VERSIONがunsetなら、Bashツールはbashで動いています。コマンドが126で落ちる状態のままなら、指定が効いていないか、原因が別にあります。
回避策が手放すもの
シェルをbashに替えると、Bashツールが読む起動ファイルは~/.bashrc側になり、~/.zshrcのエイリアス、関数、PATHの調整は見えなくなります。zsh側にしかPATHを書いていない環境では、brewやnvm経由のコマンドが見つからなくなることがあります。この副作用の詳しい扱いは、zshでloginが走り固まる問題の解説にまとめた内容と同じです。
sandboxを残したまま特定のコマンドだけ外す手もあります。sandbox.excludedCommandsにパターンを書くと、一致したコマンドはサンドボックスの外で動きます。
{
"sandbox": {
"enabled": true,
"excludedCommands": ["docker compose *"]
}
}ただし、これは原因を避ける設定ではありません。パターンは呼び出しのテキストに照合され、一致したコマンドはファイルにもネットワークにも制限なく触れます。チェーンした呼び出しは、すべてのコマンドが一致しないとサンドボックスに残ります。しかも、126の原因である内側スクリプトがzshで落ちる問題にexcludedCommandsが効くかどうかは、issueでは試されていません。全コマンドが落ちる今回の症状では、全部を除外することになり、サンドボックスを切るのと変わりません。
もう1つの選択肢は、sandbox.enabledをfalseに戻すことです。症状は消えますが、Bashコマンドがファイルにもネットワークにも制限なく触れる状態に戻ります。設定の意味はsandbox.enabledの解説にあります。隔離を残したいなら、先にシェルの側で避けるほうが損失は小さくなります。
bubblewrapとsocatが入っているか
Linuxでbwrapそのものが動かないなら、zshとは無関係の失敗です。sandboxingのドキュメントによれば、LinuxとWSL2のサンドボックスはbubblewrapとsocatに依存します。Ubuntu/Debianならsudo apt-get install bubblewrap socat、Fedoraならsudo dnf install bubblewrap socatで入ります。何が足りないかは、/sandboxのDependenciesタブに出ます。入れて再起動してもタブが出なければ、依存はそろっています。
Ubuntu 24.04以降では、既定のAppArmorポリシーがbubblewrapのユーザー名前空間の作成を止める点も、同じドキュメントに載っています。コンテナ内でCan't mount proc on /newroot/proc: Operation not permittedが出る場合は、enableWeakerNestedSandboxをtrueにする手順がドキュメントのトラブルシューティングにあります。
他のsandbox失敗との見分け方
sandbox有効のBashツールが動かない原因は、zsh以外にも複数あります。症状から行き先を決めます。
| 症状 | 疑う原因 | 参照先 |
|---|---|---|
| 全コマンドが1秒余りで126、出力は空、ログインシェルがzsh | 疑う原因内側スクリプトのzsh非互換(本記事) | 参照先上の回避策 |
setgroupsへの書き込みがPermission deniedで落ちる | 疑う原因apply-seccompの権限エラー | 参照先apply-seccompの原因と対処 |
unshare(CLONE_NEWUSER)がEINVALで落ちる | 疑う原因ユーザー名前空間の作成失敗 | 参照先unshare EINVALの原因 |
bwrapが見つからない、/sandboxのDependenciesタブに不足が出る | 疑う原因bubblewrapまたはsocatの未導入 | 参照先上の「bubblewrapとsocatが入っているか」 |
| psqlやsshだけ繋がらない | 疑う原因ネットワーク経路の制限 | 参照先sandboxから繋がらない原因 |
| コマンドは動くが「Running…」で戻らない | 疑う原因zshスナップショットのlogin行 | 参照先zshでloginが走り固まる原因 |
見分けるうえで最も役に立つのは、出力が空かどうかです。上の表で本記事に当たるのは「出力が完全にゼロ」の行だけで、他の行は何らかのエラー文字列が出ます。
issueの状況と、直るまでの見通し
issueの状態はopenで、ラベルはbug・has repro・platform:linux・regression・area:sandboxです。報告者の調査では、回帰の範囲は次のとおりです。
- 2.1.270、2.1.286、2.1.289、2.1.291では、同じホストでBashツールが動く
- 2.1.302と2.1.309では、全コマンドが126で失敗する
- つまり不具合は2.1.291から2.1.302の間に入り、2.1.309でも残っている
報告者はNixのストアに残っていた各版のビルドを同じホストで動かして切り分けたと書いており、ホスト側の条件は固定されています。名前空間のsysctl、AppArmorやSELinuxの干渉、引数の長さ超過、ユーザー設定は、それぞれ除外済みです。
報告者が出した要望は2つあります。
- 内側スクリプトをシェルに依存しない書き方にする、または想定したシェルを強制する
- 意図的な終了でも、標準エラーと
--debugに何かを出す。何も出力しないまま126で終わらせない
関連として挙がっているのは、macOSのSeatbelt版でzshの包み方が原因になった#77466、Fedoraとzshで/proc/self/fd/3が出る#51865、fd 3まわりの#52118と#51837です。報告者の整理では、このうち#51865、#52118、#51837は、期限切れか重複でクローズされています。
修正が入ればchangelogに載るはずなので、アップデートのたびに該当項目を探し、直ったらSHELL=/bin/bashやCLAUDE_CODE_SHELLの指定を外して元の挙動に戻す流れになります。
まとめ
LinuxでログインシェルがzshのときにsandboxのBashツールが全コマンド空出力の126で落ちるなら、内側スクリプトのexec 3<&-がzshで失敗している可能性があります。zsh -cとbash -cで同じ2行を比べれば、原因の型を切り分けられます。
当面の避け方は、SHELL=/bin/bash claudeでの起動です。代償として~/.zshrcの設定がBashツールから消えるので、必要なPATHは~/.bashrcにも置いておくと、回避策が別の失敗を呼びません。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。