Claude CodeがDocker DesktopのコンテナでBashを拒否する原因と回避策
Docker Desktop(WSL2)のLinuxコンテナでClaude CodeがBashを拒否する不具合の原因と、Windowsフォルダーを読み取り専用で1つマウントして通す回避策です。
Docker DesktopのWSL2バックエンドで動くLinuxコンテナの中では、Claude CodeがBashの実行を丸ごと拒否することがあります。原因は、コンテナのカーネルが「WSL2」と名乗るのに、隠すべきWindowsドライブが1つもマウントされていないことです。回避策は、Windows側のフォルダーを読み取り専用で1つマウントして起動し直すだけです。
この件はGitHubのissue(anthropics/claude-code #104796)に、再現手順つきで報告されています。起票は2026年10月24日です。
拒否されるときに出るメッセージ
報告されているエラーは次の1文です。
A shell tool was granted, but no Windows filesystem mount was recognized in the WSL mount table, so the Windows side could not be put out of the sandboxed shell's reach - the run was refused.訳すと「シェルツールは許可されたが、WSLのマウント表にWindowsのファイルシステムが見当たらず、Windows側をサンドボックス内のシェルから届かない状態にできなかったため、実行を拒否した」です。拒否の主体はサンドボックスで、コマンドそのものが失敗したわけではありません。
報告者の環境は次のとおりです。
- ホストはWindows 11で、Docker Desktop 29.8.2のWSL2バックエンド
- コンテナはUbuntu 24.04にNode 22、
bubblewrap、socatを入れたイメージ - Claude Codeはnpm版の2.1.309(2.1.310は未検証と明記)
- 用途はセルフホストのGitHub Actionsランナー(非対話のCI)
なぜコンテナだけで起きるのか
仕組みは報告の本文から次のように読めます。
拒否に至る流れ
- 1
WSLだと判定する
Claude Codeはコンテナ内の
/proc/versionを見ます。Docker Desktopのコンテナは、WSL2のカーネル(6.6.87.1-microsoft-standard-WSL2)をホストと共有するため、中身が普通のUbuntuでもWSLと判定されます。 - 2
Windowsのマウントを探す
WSLと判定すると、サンドボックスはWindowsのドライブをシェルから見えなくするために、マウント表からWindows側のマウントを探します。
- 3
見つからず拒否する
コンテナには、ホストから明示的にマウントしない限りWindowsのドライブがありません。隠す対象が見つからず、準備できているか判断できないので、念のためBashの実行を拒否します。
報告者が期待する挙動は、コンテナを素のLinuxとして扱ってbubblewrapのサンドボックスを動かすことです。最低でも「WSLのカーネルでWindowsのマウントが無い」状態は、拒否ではなく「隠すものが無い」と扱ってほしい、としています。
「分からないものは通さない」という拒否は、サンドボックスの方針としては筋が通っています。ただ、今回はCIの実行基盤が丸ごと止まる側に倒れた形です。
報告された再現手順
issueの手順は、サンドボックスが動く前提条件の整え方まで含まれているので、手元で試す人の参考になります。
- Docker Desktop(WSL2バックエンド)でUbuntu 24.04のイメージを作り、Node 22、
npm i -g @anthropic-ai/claude-code@2.1.309、bubblewrap、socatを入れる - Dockerの既定のseccompプロファイルは
unshare・clone・clone3・mount・umount2・pivot_root・setnsを止めるため、これらを許すプロファイルで起動する - コンテナ内で次のコマンドが成功することを確かめる
bwrap --ro-bind / / --proc /proc --dev /dev --unshare-all trueallowed_tools: [Bash]を指定したevalケース(echo ok > out.txtを実行させ、out.txtの存在を採点)を持つプラグインを作る- 次のコマンドで実行する。Windowsホストからのバインドマウントは付けない
TMPDIR=$HOME/tmp claude plugin eval . --trust-plugin \
--allow-tools Bash --runs 1 --ablation noneこの手順では全ケースが上のメッセージでエラーになります。再現に使われているのはclaude plugin evalで、対話セッションでの再現は報告に含まれていません。評価の実行が名前指定で失敗する別の症状はclaude plugin evalが名前指定で失敗する原因にあります。
回避策は読み取り専用マウントを1つ足すこと
issueに書かれた回避策は、Windows側のフォルダーを1つ、読み取り専用でコンテナに渡すことです。
docker run --rm \
--security-opt seccomp=./seccomp-bwrap.json \
-v 'C:\empty:/mnt/x:ro' \
my-claude-runner:latestC:\emptyは空のフォルダーで構いません。報告では「Windowsのフォルダーを:roで1つ渡す」だけで同じケースがサンドボックス内で動き、評価も通ったとされています。コンテナ側のパス/mnt/xは任意の名前でよいはずですが、報告者が試したのはこの1例だけです。
ここで押さえておく点が3つあります。
- 渡すのは空のフォルダーにする。サンドボックスが隠す対象になるだけで、ホストの中身を共有する意図はない
- 必ず
:roを付ける。書き込み可能にする理由が無い - これは報告者が見つけた回避策であり、Anthropicが案内している手順ではない
CIランナーのように起動コマンドをテンプレート化している環境では、このマウントを足した一行をランナーのイメージ起動定義に入れておくと、再発を避けられます。
Windows側を隠す理由は、WSLの起動の仕組みにある
そもそもサンドボックスがWindows側を気にするのは、WSL2ではLinuxからWindowsの実行ファイルを呼べるためです。ドキュメントによると、cmd.exe、powershell.exe、/mnt/c/配下のバイナリを起動すると、WSLはその起動をUnixソケット経由でWindowsホストに渡します。つまり、隠さないままBashを許せば、サンドボックス内のコマンドがWindows側へ手を伸ばせる経路が残ります。
この経路を許すかどうかは、サンドボックスのUnixソケット設定に従います。ブロックするには任意のseccompフィルターの導入が必要で、起動を許したいときはallowAllUnixSocketsを使います。ただし後者はすべてのUnixソケットを開く設定で、ドキュメントも広い許可だと位置づけています。
今回の拒否は、この「Windows側を届かなくする」処理の前提が崩れたことで起きています。コンテナの中にはもともと/mnt/cのような経路が無いので、実害の面では隠す必要がありません。それでもカーネルの名乗りだけで判定してしまうため、準備が整っていないと見なして実行を止めます。
CIに組み込むなら、起動直後に疎通を確かめる
セルフホストランナーでは、ジョブの途中で拒否されるより、最初のステップで落ちるほうが原因を追いやすくなります。評価を回す前に、Bashを使う最小のケースだけを流す確認を入れる構成です。
steps:
- name: コンテナ内でbwrapが動くか
run: bwrap --ro-bind / / --proc /proc --dev /dev --unshare-all true
- name: Bashのサンドボックスが拒否されないか
run: |
TMPDIR=$HOME/tmp claude plugin eval ./smoke --trust-plugin \
--allow-tools Bash --runs 1 --ablation none1つ目で落ちるなら、seccompプロファイルや/procなど、bwrap側の問題です。1つ目が通って2つ目だけが拒否メッセージで落ちるなら、本記事の症状と見て、起動定義のマウントを疑えます。./smokeはecho ok > out.txtだけを実行させる1ケースのプラグインを想定した名前で、issueの手順4をそのまま小さくしたものです。
他の選択肢と、症状の見分け方
回避策の候補を、効く条件ごとに並べます。
| 選択肢 | 効く条件 | 注意 |
|---|---|---|
空フォルダーを:roでマウント | 効く条件Docker Desktop(WSL2)のコンテナで、この拒否メッセージが出る | 注意報告者の環境での結果 |
| コンテナを使わず、WSL2のディストリビューションで直接動かす | 効く条件Windows上でサンドボックスを使いたい | 注意ドキュメントはサンドボックスの対象としてmacOS・Linux・WSL2を挙げる |
| ネイティブのLinuxホストにランナーを移す | 効く条件WSL2のカーネルを共有しない | 注意Windows 11のDocker Desktopという前提を外す |
| サンドボックスを使わない設定にする | 効く条件隔離をコンテナ側に任せる | 注意この拒否経路で効くかは報告に書かれていない |
最後の行は特に注意が必要です。ドキュメントによると、ネイティブWindowsではClaude Codeがサンドボックスなしでコマンドを実行します。しかし今回の拒否は「サンドボックスの初期化に失敗した」のとは別の経路として報告されており、設定でサンドボックスを切れば通るかどうかは、issueにも書かれていません。試すなら、評価の実行で結果を見てから採用してください。
似た症状との見分け方
コンテナのsandbox失敗には原因の違う症状がいくつかあります。メッセージで切り分けられます。
| 出るメッセージ | 原因 | 参照先 |
|---|---|---|
no Windows filesystem mount was recognized | 原因WSL2カーネルでWindowsのマウントが無い | 参照先本記事 |
bwrap: Can't mount proc on /newroot/proc: Operation not permitted | 原因非特権コンテナで新しい/procをマウントできない | 参照先enableWeakerNestedSandboxの記事 |
bwrap: Can't mkdir /mnt/c/Program Files/ClaudeCode | 原因WSL2本体で、サンドボックスがWindowsドライブに書き込める場合にWindows側の管理設定フォルダーが無い | 参照先WSL2 sandbox失敗の記事 |
2行目の対処は、ドキュメントに載っています。非特権コンテナではenableWeakerNestedSandboxをtrueにすると、コンテナの既存の/procをbind-mountして動かせます。ただし外側のコンテナがすでに隔離の境界を担っている場合に限る設定で、サンドボックス内のコマンドからプロセス情報が見えるようになります。今回の拒否はこの設定では解決しません。マウント表にWindowsが無いことが原因で、/procの問題とは別だからです。
3行目のWSL2本体の症状は、修正のあった変更履歴が公開されています。WindowsにC:\Program Files\ClaudeCodeが無いままWSL2で動かしたときの失敗で、今回のコンテナとは前提が異なります。
コンテナの隔離とBashのsandboxをどう組み合わせるか
CIランナーでは、コンテナ自体が境界を担います。ドキュメントは、カスタムコンテナの中でさらに組み込みのBashサンドボックスを重ねられるとしています。重ねるなら、今回のようにコンテナのカーネルがWSL2のときは追加の準備が要ります。
準備の手順をまとめると次のとおりです。
- seccompプロファイルで、
bwrapに必要なシステムコールを許す - 手元で
bwrapのテストコマンドを実行して成功させる - Windowsのフォルダーを空で
:roマウントする - 必要なら
enableWeakerNestedSandboxを検討する(外側が隔離を担う場合のみ)
どの隔離を何に任せるかの全体像はClaude Codeのサンドボックスの選び方にまとめています。サンドボックスが動かないときに、無防備なまま走らせず止める設定はsandbox.failIfUnavailableの記事が扱っています。
Docker Desktop側の別の不具合として、Claude Desktopの操作でDockerが落ちる症状もあります。こちらはサンドボックスではなくAF_UNIXソケットが原因で、Claude Desktop経由でDockerが落ちる原因と回避策に書いています。
修正を待つ間にやること
修正バージョンが出るまでは、次の順で動くと無駄がありません。
- 拒否メッセージが出たら、コンテナ内の
/proc/versionにmicrosoft-standard-WSL2が含まれるか見る - 含まれるなら、空フォルダーを
:roでマウントして起動し直す - 通ったら、起動定義にマウントを組み込んで固定する
- 新しいバージョンを使うときは、一度評価のケースを流して拒否が消えたか確かめる
報告者は2.1.310では試していないと明記しています。アップデート後に同じメッセージが出るかを、自分の環境で確かめるのが確実です。