席を外して戻ると、エージェントが丁寧に待っている。「sandbox の制限で実行できませんでした。通常のターミナルで次のコマンドを実行してください」。任せたのは自分なのに、呼び出されるのも自分だ。
筆者の Claude Code(Pro $20/月、Max $100/月〜)の会話記録を 30 日分数えた。人が呼び戻されたのは sandbox の外で動かす前の承認と「! で実行して」の依頼で、拒否と依頼の全体の 23% だった。許可は何度も足してきたはずなのに、型の 80% は手付かずか、直した後も起きていた。
この記事では、詰まりを記録から数えて「設定で消す」「手順で消す」「開けない」に分ける。設計段階の見積もりはsandbox 制約の見積もり、permissions はリスク別のガイドにある。
人が呼び戻される経路
拒否されたコマンドを、Claude は dangerouslyDisableSandbox を付けて外でやり直せる。公式の「sandbox 外での再試行」だ。やり直さないときは「! で実行してください」と頼んでくる(そして席に戻るまで律儀に待つ)。
sandbox の外で動くとき、誰が承認するかは permission mode で決まる。Manual mode では「Bash command (unsandboxed)」の確認が出る。auto mode では分類器が判定する。一致する allow ルールがあれば確認なしで通る。excludedCommands に一致したコマンドも同じ流れを通る。
つまり excludedCommands は承認を省く設定ではない。ファイル制限と通信の proxy を外す設定だ。これが allow ルールや auto mode と重なった所が、人を呼ばずに sandbox の外で動く経路になる。承認を減らしたくて足した 1 行が、そこで無人の通用口になる。
まず記録を数えさせる
会話記録は ~/.claude/projects/<project>/<session>.jsonl に平文で残り、既定では 30 日で消える。サブエージェントの記録は <session>/subagents/ に別に置かれる。記録は画面に出た秘密まで覚えているので、結果は型と件数だけで受け取る。
~/.claude/projects/ の直近 30 日の transcript(subagents/ 配下を含む)から、sandbox の拒否・
permission の拒否・人へのコマンド実行の依頼・sandbox 外での再試行を、拒否された対象ごとに数えて。
出力は型・件数・最終発生日だけ。コマンド全文や秘密を含む文字列は出さない。
件数の多い順に「設定で消す / 手順で消す / 開けない」に分け、理由を付けて。
excludedCommands に足す案は、実体が sandbox の中から書き換えられない場所にあり、任意のファイルや指示を実行しないものに限って。
エージェントは次の所で数え間違えるので、指示に 1 行ずつ足す。
| 数え間違い | 確かめさせること |
|---|---|
| ファイルやログの中身に含まれる「Operation not permitted」を拒否と数える | コマンド自身の診断行と、出力末尾の違反ブロックだけ数える |
| resume や fork で同じ行が複数の記録に写る | 事象の ID で重複を除く |
| サブエージェントの拒否を数え落とす | <session>/subagents/ 配下の記録も読ませる。親の記録には写らない |
| 直した型を修正候補に出す | 設定の変更履歴と突き合わせ、最後の変更より後に、修正を含むブランチで再発した型だけ残す |
型ごとに 3 つへ分ける
| 詰まりの型 | 筆者の記録 | 対処 | 理由 |
|---|---|---|---|
| キャッシュへの書き込み拒否 | パッケージマネージャのキャッシュ | 設定で消す:filesystem.allowWrite にそこだけ足すか、キャッシュ先を $TMPDIR に向ける | 外で動くコードは増えない |
| 仕事に要るホストへの通信拒否 | 配布元・クラウド API | 設定で消す:network.allowedDomains に足す | 中のまま通る |
| 広すぎる deny の巻き込み | .env.* の禁止が .env.example まで止めた、190 件 | 設定で消す:deny を秘密を含むファイル名に絞る | 守るものは変わらない |
| 利用統計の送信拒否 | CLI のログ収集ホスト、100 件超 | 手順で消す:テレメトリを環境変数で切る | 許すと送信先が増えるだけ |
| 外で実行する道具を自作している場合:中で試さずに外へ出る | 自作の外部実行ツール、240 件 | 手順で消す:外へ出す道具の説明に「中で一度失敗してから使う」と書き、拒否の文面を理由に付けさせる | 見込みで人を呼ばない |
| 起動形のせいで除外に一致しない | > file・cd &&・$(...) 付きの呼び出し | 手順で消す:素の 1 コマンドで呼ぶ | 呼び出しごと sandbox に残る |
| sandbox の中から claude を起動 | API ホストへの通信拒否、182 件 | 開けない:API ホストは許可せず、claude * も除外しない。claude を呼ぶテストは CI で回す | 許可すると中の全コマンドの送り先になる |
| 資格情報ファイルの読み取り拒否 | CLI の認証設定 100 件。仲介用のプロキシで 0 件に | 開けない:資格情報を仲介する経路で通す。公式の mask は macOS ではファイルに効かない | 開けると中の全コマンドが読める |
| 他プロセスの停止 | kill の依頼、23 件 | 開けない:人に残す | 止める相手を選ぶのは人 |
件数最多の 384 件は、worktree で隔離したセッションの「git が外に出ないと文面から確かめられない」という拒否だ。設定では外せないが、拒否文が書き直し方を示す(公式)ので、エージェントは自分で進む。一番多い型が、人を一度も呼ばない型だった。
筆者の環境は allowUnsandboxedCommands: false で公式の再試行を切り、承認ダイアログを出してから外で実行する自作のツールを使っている。表の「自作の外部実行ツール」はその記録だ。説明に「sandbox で動かないときにも使ってよい」と書いていたため、承認を得て実行した 259 件のうち、直前の Bash が拒否されていたのは 19 件だけだった。エージェントは書かれたとおりに使っただけだ(呼んでいたのは自分の書いた 1 行だった)。公式の再試行は失敗後に出る作りなので、この比率はそのまま当てはまらない。
開けない側の基準
何回呼ばれても、ここは開けない。呼ばれる手間より、開いた穴のほうが高くつく。
1. 外に出してよいのは、sandbox の中から書き換えられない実体だけ。 excludedCommands に一致したコマンドは、ファイル制限も通信の proxy も無しで走る(公式)。作業ツリーや $TMPDIR のスクリプトは足さない。bash・python3・make・npm・claude のように任意のファイルを実行するものも同じだ。エージェントが書いたファイルが外で走る。git * も、hook が作業ツリーにあれば hook ごと外で走る。
2. パスの * で場所を信用しない。 規則の * は任意の文字列に一致し、.. も例外ではない。筆者の設定の「置き場/*」は 置き場/../../一時ディレクトリ/x.sh にも一致し、そこに書いたスクリプトが外で走った。鍵を足したつもりが、実際は裏口を足していた。登録はコマンド名か、絶対パス 1 本で書く。
3. git fetch * のように引数を前方一致で絞った形を、安全策と数えない。 公式 docs も脆いとしている。
4. 資格情報と他プロセスへの経路は開けない。 Unix socket も全許可の allowAllUnixSockets は使わず、個別のパスで許す。
除外リストから、パスを含む行と、任意のファイルを実行する代表的なコマンドを出すならこうなる。bash・zsh・fish で同じ結果になる。
jq -r '.sandbox.excludedCommands[]? | select(test("/|^(bash|sh|zsh|fish|python3?|node|npx|tsx|bun|bunx|deno|uv|uvx|ruby|perl|make|npm|pnpm|yarn|docker|git|env|xargs)( |:|$)"))' ~/.claude/settings.json
パスを含む行は、パスに * が無く、場所が作業ツリー・$TMPDIR・filesystem.allowWrite の下でなければ残してよい。例は /usr/bin/top *。just・go run・cargo run も make と同じだ。プロジェクトの settings も見る。
記録に出ないもの
記録は手元のマシンのことしか知らない。別のマシンやクラウドのセッションは入らない。30 日で忘れるので、長い比較には日々の要約を別に残す。
まとめ
近道は許可を足すことではなく、記録を数えて 3 つに分けることだった。エージェントが賢くなるほど、止まる理由は能力より境界の引き方になる。自走させたいなら、まず 30 日分の記録に「どこで人を呼んだ?」と聞いてみるとよい。



