Claude Media
Claude CodeでTestcontainersのテストをsandbox越しに動かす手順

Claude CodeでTestcontainersのテストをsandbox越しに動かす手順

Testcontainersを使う統合テストがClaude Codeのsandboxで失敗するとき、docker*を除外しても直らない理由と、テスト実行コマンドだけを外へ出す最小の設定を示します。

Claude Codeのsandboxを有効にしたままTestcontainersの統合テストを走らせると、コンテナの起動前にDockerへの接続で止まります。トラブルシューティングの節はdockerコマンドの失敗にdocker compose *のようなexcludedCommandsを勧めていますが、Testcontainersのテストでは、その除外だけでは直りません。テストを起動するコマンドの文面にdockerが出てこないからです。ここでは、除外の対象をテスト実行コマンドに置き換える手順と、外へ出したときに何が開くかを順に見ます。

Testcontainersのテストが止まる位置

Testcontainersは、テストプロセスの中からDockerデーモンへ接続してコンテナを起動します。接続先はDOCKER_HOSTか、Testcontainersが検出した既定のソケットです。Testcontainersの設定ページには、DOCKER_HOST = unix:///var/run/docker.sockの形と、ソケットのパスを上書きするTESTCONTAINERS_DOCKER_SOCKET_OVERRIDE、公開ポートのホストを上書きするTESTCONTAINERS_HOST_OVERRIDEが載っています。

つまり、sandboxの内側で動くテストプロセスは、Dockerのソケットに接続しようとします。sandboxのドキュメントは、dockerをsandboxと両立しないコマンドとして扱っています。Testcontainersに関する記述はありません。以降は、sandboxの公式な挙動を順に当てはめた切り分けです。

止まる箇所は2か所あります。

原因

sandboxで統合テストが止まる2か所

  • Dockerソケットへの接続

    テストプロセスがデーモンに接続できないと、コンテナの作成に進めません。

  • 公開ポートへの接続

    コンテナが起動しても、ホスト側に公開されたポートへの接続がLinuxとWSL2では通りません。

後者は見落としやすい点です。ドキュメントによると、LinuxとWSL2でsandbox内のコマンドのlocalhostはそのコマンド専用になり、ホスト上のサーバーには届かないと説明しています。コンテナ内のデータベースが例として挙げられています。macOSではnetwork.allowLocalBindingをtrueにする選択肢がありますが、localhostの他のサービスにも届くようになる点には注意が要ります。

docker*を除外してもTestcontainersが直らない理由

excludedCommandsは、Claudeが送るBash呼び出しの文面と照合されます。ルールは次のとおりです。

  • 末尾を *にしない限り、パターンは完全一致になる
  • 呼び出しの中の全コマンドが一致しないと、sandboxの外へ出ない
  • 文面だけを見るので、内部でdockerを呼ぶスクリプトやmakeのターゲットは一致しない

たとえば./gradlew integrationTestやpytest tests/integrationを実行しても、呼び出しの文面にdockerはありません。docker *を除外しても、テストプロセスはsandboxの中で動き続けます。コンテナ関連の除外は、docker compose upを直接叩く流れにしか効きません。基本の書き方はsandbox.excludedCommandsの解説にあります。

テスト実行コマンドだけを外へ出す

Testcontainersを使う統合テストは、単体テストと分けて呼べるようにしておきます。ここでは例として、package.jsonに統合テスト専用のスクリプトがある構成を想定します。

{
  "scripts": {
    "test": "vitest run --exclude 'tests/integration/**'",
    "test:integration": "vitest run tests/integration"
  }
}

除外するのは、統合テストを呼ぶコマンドだけです。

{
  "sandbox": {
    "enabled": true,
    "excludedCommands": ["npm run test:integration"]
  }
}

*を付けない書き方は完全一致で、引数付きの呼び出しは外へ出ません。引数を渡す場合はnpm run test:integration *にしますが、範囲は広がります。単体テストのnpm testは除外しません。日常の変更確認はsandboxの内側で回り、コンテナが要る確認だけが外へ出る分担になります。

外へ出したコマンドで開くもの

外へ出したコマンドは、ファイルシステムの制限もネットワークプロキシも受けません。除外コマンドはフルアクセスで動くと警告されています。作業ディレクトリ内のスクリプトや、ファイルに作用するツールを除外すると、Claudeがそのファイルを書き換えてから、sandboxの外で実行できる点も指摘されています。

npm run test:integrationはまさにこの型です。実行内容はpackage.jsonのscriptsか、そこから呼ばれるテストコードで決まり、どちらもClaudeが編集できます。歯止めは次の3層で組みます。

手順

除外したテスト実行の歯止め

  1. 1

    許可ルールを足さない

    除外したコマンドも通常の権限フローを通ります。allowルールで事前承認しなければ、Manualモードでは実行のたびに確認が出ます。

  2. 2

    確認画面で中身を読む

    確認が出た時点で、スクリプトの内容とテストコードの差分を見ます。ドキュメントにも、確認時にスクリプトをレビューするよう書かれています。

  3. 3

    編集禁止ルールを併用する

    実行内容を決めるファイルにEditのdenyルールを置くと、Claudeによる書き換えを止められます。

3つ目の例は次のとおりです。package.json全体を禁止すると依存の追加もできなくなるので、スクリプト本体を別ファイルに切り出した構成のほうが向いています。

{
  "permissions": {
    "deny": ["Edit(/scripts/run-integration.sh)"]
  }
}

この例では、統合テストの起動をシェルスクリプトに寄せます。excludedCommandsのエントリはbash scripts/run-integration.shのような完全一致の形にします。パス指定の仕組みは権限ルールの書き方に従い、/で始まる形はプロジェクトの作業ディレクトリ基準になります。

ソケットだけを許可する道を選ぶ前に

コマンド全体を外へ出さず、Unixソケットだけを許可する設定もあります。ただし、プラットフォームで挙動が分かれます。

環境設定仕様
macOS設定network.allowUnixSockets仕様ソケットのパスを列挙する
Linux・WSL2設定network.allowAllUnixSockets仕様パス単位の指定は無視される。許可するなら全ソケット

/var/run/docker.sockの許可がDockerデーモンの操作権限、つまりホストへの実質的なアクセスにつながると警告されています。Linuxでは全Unixソケットを開ける設定しかなく、sandboxの効果はかなり薄れます。

しかも、ソケットを開けても前節の2か所目が残ります。Linuxでコンテナの公開ポートへ接続するには、ドキュメントの案内どおりそのコマンドをexcludedCommandsで外へ出すほかありません。Testcontainers向けにソケットだけを許可して動くという記述は、sandboxのドキュメントにありません。Linuxでは、テスト実行コマンドごと外へ出す形が、現実的な選択になります。

ClaudeにCLAUDE.mdで手順を教える

sandboxの失敗が出ると、ClaudeはdangerouslyDisableSandboxを付けた再実行を提案することがあります。統合テストの手順を決めておけば、そうした場当たり的な再実行を減らせます。CLAUDE.mdには次のような断片を置きます。

## 統合テスト(Testcontainers)
 
- 統合テストは `npm run test:integration` で実行する
- `docker` を直接実行しない。コンテナはテストコードが起動する
- 単体テストは `npm test` で、統合テストとは別に実行する
- 統合テストが接続エラーで失敗したら、sandbox設定を変える前に報告する

組織でallowUnsandboxedCommandsをfalseにしている厳格なsandboxでは、Claudeの再実行は無視されます。その場合、コマンドをsandboxの外へ出す手段はexcludedCommandsのエントリだけです。注意点として、管理設定や--settingsで厳格にした組織では、リポジトリの.claude/settings.jsonにあるexcludedCommandsは無視されます。そのときは、ユーザー設定か管理設定にエントリを置きます。詳しくはexcludedCommandsが効かない原因にまとめています。

設定が効いているかを確かめる

Manualモードで、除外したコマンドを実行させます。

npm run test:integration を実行して

確認画面のタイトルが「Bash command (unsandboxed)」になれば、エントリが一致しています。sandbox内のままなら確認は出ず、Dockerへの接続エラーでテストが止まります。止まったときの切り分けは次のとおりです。

症状見る場所
確認画面が出ず、そのままDocker接続で失敗する見る場所エントリが完全一致か。cdやリダイレクトを含む呼び出しになっていないか
cd tests && npm run test:integrationのように連結している見る場所連結した全コマンドが一致しない限り、sandbox内のまま。cdを含む呼び出しは常にsandbox内
確認が出て実行されるが、テストが依存を取得できない見る場所外へ出た後の失敗なので、sandbox以外の原因。DockerやTestcontainersの設定を見る
チーム共有の設定が効かない見る場所厳格なsandboxでは、リポジトリ内の設定ファイルのエントリは無視される

コンテナの掃除用Ryukはsandboxと別の話

Testcontainers(Java版)は、テスト終了後の掃除にRyukという常駐コンテナを使います。公式のConfigurationページによると、RyukはPrivilegedコンテナとして起動する必要があります。特権コンテナを許さない環境では、TESTCONTAINERS_RYUK_DISABLEDをtrueにしてRyukを止められます。ただし、その場合の掃除は、JVMの終了時に行われるものに限られます。

これはsandboxの制限ではなく、Dockerの実行環境側の事情です。sandboxは、Claudeが起動したシェルコマンドとその子プロセスを囲います。コンテナはDockerデーモンが起動するため、その外にあります。sandboxの回避策としてRyukを無効にする必要はありません。逆に、Ryukを無効にしても、sandbox内でDockerに接続できない問題は解消しません。

除外が合わないときの選択肢

テスト実行をそのつど外へ出す運用が重いなら、囲う範囲を変える手があります。

  • sandboxを使わず、Claude Code全体をコンテナで隔離する: ドキュメントは、プロセス全体をdevcontainer、仮想マシン、sandbox runtimeで囲む構成を、sandboxの代わりに挙げています。Dockerまわりの扱いはClaude CodeでDockerを使う解説にあります
  • 統合テストを自分で実行する: 実行はシェルモードの!か別ターミナルで行い、結果だけをClaudeに読ませる。Claudeの外へ出す範囲が、いちばん狭くなります
  • 統合テストの失敗だけ見せる: 失敗ログをClaudeに渡し、修正はsandbox内で行う。コンテナが要る確認だけ自分で回します

どの構成でも、統合テストでコンテナを使うかぎり、Dockerデーモンの操作権限はどこかに残ります。除外の範囲は、そのコマンドが何をできるかで決まります。

まとめ

Testcontainersの統合テストは、docker *ではなくテスト実行コマンドをexcludedCommandsに入れて外へ出します。範囲は完全一致で絞り、allowルールで事前承認せず、実行内容を決めるファイルには編集禁止を掛けます。外へ出る範囲が小さいほど、Claudeが書き換えたコードを無防備に走らせるリスクは小さくなります。

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