Claude Code を「言うことを聞く」エージェントにする4つのレイヤー — CLAUDE.md・permissions・Hooks・Sandbox の使い分け

Claude Codeを意図どおりに動かす4つの制御レイヤー:CLAUDE.md、permissions、Hooks、Sandbox Claude Code

はじめに

Claude Code を使い始めると、多くの人が同じ壁にぶつかります。

  • テストを走らせるたびに「実行していいですか?」と聞かれて、手が止まる
  • 逆に「全部任せる」モードにしたら、.env を読まれたり、履歴を書き換える git push --force が走ったりしてヒヤッとする
  • 「テストは uv run pytest で動かして」「型は app/schemas/ に置いて」と、毎回同じ説明をプロンプトに書いている

これらは全部、Claude Code に「どこまで自由に動いてよいか」「何を前提に動いてほしいか」を伝えていないことが原因です。Claude Code には、そのための仕組みが4つのレイヤーとして用意されています。

この記事では、その4つ(CLAUDE.md、settings.json の permissions、フック、サンドボックス)を「どこに置くと、どこまで効くか」という共通の枠組みから整理し、架空の Python プロジェクトを題材に、実際に設定を組み立てていきます。

読み進める前に、変化のイメージをつかんでおきましょう。左が「制御する前」、右が「制御した後」です。

制御する前は確認疲れと暴走が起き、制御後は安全な操作が自動化され危険な操作は実行前に止まる様子

図1: 制御レイヤーを整えると、確認疲れと暴走の両方が減る(静止画版: fig01_before_after.png)

この記事で学べること

  • 設定ファイルとメモリファイルの「スコープ」と「優先順位」の考え方
  • CLAUDE.md とルールファイルに「何を書き、何を書かないか」
  • permissions の allow / ask / deny の書き方と、判定順序の落とし穴
  • フックで「編集後に自動テスト」「危険コマンドを実行前にブロック」「作業完了を通知」を実装する手順
  • 2026年時点で既定になった Auto モードが、何を自動で通し、何を止めるのか

想定読者

  • Claude Code を一度は触ったことがあり、権限プロンプトに煩わしさを感じている方
  • チームで Claude Code を使うために、共有できる設定を整えたい方
  • シェルスクリプトと JSON を読める程度の知識があれば十分です

前提

本記事の内容は、2026年9月時点の Claude Code 公式ドキュメント(code.claude.com/docs)に基づいています。Claude Code は更新が非常に速く、イベント名や既定値が変わることがあります。迷ったら公式ドキュメントを確認してください。


全体像:4つのレイヤーは「強制力」で並べると理解しやすい

Claude Code の振る舞いを制御する仕組みは複数ありますが、「Claude の判断にどれだけ依存するか」という軸で並べると、役割の違いがはっきりします。

CLAUDE.md、permissions、Hooks、Sandboxの4層を強制力の弱い順に並べた図

図2: 上から順に「お願い」→「ルール」→「強制」→「隔離」と強制力が上がる(静止画版: fig02_control_layers.png)

レイヤー 仕組み 役割 強制力
1. 伝える CLAUDE.md、.claude/rules/ プロジェクトの前提・規約を毎セッション読ませる なし(Claude が判断材料にする)
2. 決める settings.json の permissions ツール呼び出しを allow / ask / deny に仕分ける Claude Code 本体が機械的に判定
3. 強制する フック(Hooks) ツール実行の直前・直後に自前の処理を割り込ませる exit 2 で実行そのものを止められる
4. 隔離する サンドボックス Bash とその子プロセスを OS レベルで隔離 ファイル・ネットワークの到達範囲を物理的に制限

ここで大事なのは、CLAUDE.md は「強制」ではないという点です。公式ドキュメントも、CLAUDE.md や自動メモリは「コンテキストであって、強制される設定ではない」と明言しています。「絶対に本番 DB を触らないで」と CLAUDE.md に書いても、それは Claude への依頼にすぎません。確実に止めたいなら、permissions の deny か、PreToolUse フックを使う必要があります。

逆に、フックやサンドボックスで縛るだけでは、Claude は「なぜ止められたのか」「代わりにどうすればよいか」を知りません。上のレイヤーで前提を伝え、下のレイヤーで安全を担保する。この組み合わせが基本形です。


題材:架空の FastAPI プロジェクト「shiftboard-api」

この記事では、次のような小さなプロジェクトを想定して設定を組み立てます。書籍やチュートリアルでよく見る Next.js 製アプリではなく、Python のバックエンドを題材にしたのは、読者の環境で置き換えやすい「コマンド名」が違うだけで、考え方はそのまま使えることを示したいからです。

  • 用途: 店舗スタッフのシフトを管理する REST API
  • 技術スタック: Python 3.12 / FastAPI / SQLAlchemy / Alembic(DB マイグレーション)/ PostgreSQL(Docker Compose)
  • ツール: パッケージ管理は uv、Lint とフォーマットは ruff、テストは pytest
  • ディレクトリ構成
shiftboard-api/
├── app/
│   ├── api/          # ルーター(FastAPI の endpoint)
│   ├── services/     # ビジネスロジック(シフト割当・重複判定)
│   ├── models/       # SQLAlchemy モデル
│   └── schemas/      # Pydantic スキーマ
├── alembic/          # マイグレーション
├── tests/
├── .claude/          # ← この記事で育てていくディレクトリ
├── compose.yaml
└── pyproject.toml

「危険な操作」もこのプロジェクト向けに具体化しておきます。

操作 なぜ危険か
uv run alembic downgrade 共有 DB のスキーマを巻き戻し、他メンバーの作業を壊す
docker compose down -v ボリュームごと削除され、ローカル DB のデータが消える
git push --force リモートの履歴を書き換える
rm -rf セッション前からあったファイルを不可逆に消す
.env の読み取り DB パスワードや API キーがモデルのコンテキストに流れ込む

まず押さえる:「どこに置くと、どこまで効くか」(スコープと優先順位)

CLAUDE.md も settings.json も、置く場所によって「誰の・どのプロジェクトで」効くかが変わります。公式ドキュメントではこれを「スコープ」と呼び、4種類あります。個別の設定に入る前に、この枠組みを先に押さえておくと、後の内容がすべて同じルールで理解できます。

4つのスコープ

ユーザー・プロジェクト・ローカル・マネージドの各settings.jsonが、自分のPCとチームメイトのPCのどのプロジェクトに効くかを示す図

図3: 同じ settings.json でも、置き場所で「誰の・どのプロジェクトで」効くかが変わる(静止画版: fig03_scopes.png)

スコープ settings.json の場所 CLAUDE.md の場所 効く範囲 向いている内容
マネージド managed-settings.json(macOS: /Library/Application Support/ClaudeCode/、Linux: /etc/claude-code/)、MDM、claude.ai の管理コンソール 同じディレクトリの CLAUDE.md 組織が配布した全 PC の全ユーザー セキュリティポリシー、コンプライアンス要件
ユーザー ~/.claude/settings.json ~/.claude/CLAUDE.md 自分の PC の全プロジェクト テーマ、出力スタイル、個人の権限ルール
プロジェクト <repo>/.claude/settings.json <repo>/CLAUDE.md または <repo>/.claude/CLAUDE.md このリポジトリ(Git にコミットすればチーム全員) チームの権限ルール、フック、コーディング規約
ローカル <repo>/.claude/settings.local.json <repo>/CLAUDE.local.md このリポジトリの自分だけ 個人の実験、自分だけの API キー

ローカルスコープについて1つ補足します。settings.local.json は、権限プロンプトで「Yes, and don’t ask again」を選んだときに Claude Code が自動で書き込むファイルでもあります。Claude Code がこのファイルを作成した場合は Git の除外設定にも自動で追加されますが、自分で手書きした場合は .gitignore への追加を忘れないでください。

優先順位:上に書いたものが勝つ

同じキーが複数のスコープに書かれている場合、優先順位の高いほうが採用されます。

マネージド、コマンドライン、ローカル、プロジェクト、ユーザーの順に優先される設定の階層図

図4: 例外として permissions.allow などのリストは「上書き」ではなく「結合」される

  1. マネージド設定(組織配布。何をしても上書きできない)
  2. コマンドライン引数(claude --settings '{...}' や --model など。そのセッション限り)
  3. ローカル(.claude/settings.local.json)
  4. プロジェクト(.claude/settings.json)
  5. ユーザー(~/.claude/settings.json)

ただし、permissions.allow や permissions.deny のようなリスト型のキーは上書きではなく結合されます。ユーザー設定の allow とプロジェクト設定の allow は両方有効になり、どこかの deny に書かれた操作はどのスコープの allow があっても止まります。「チームの deny を、自分のローカル設定で無効にする」ことはできない設計です。

マネージドポリシーが最優先な理由

マネージド設定が最優先で、CLI フラグでも上書きできないのは、組織のセキュリティポリシーを個人やプロジェクトの設定で破れないようにするためです。たとえば組織が次のように配布していれば、個人が ~/.claude/settings.json で Bypass Permissions モードを有効にしようとしても効きません。

{
  "permissions": {
    "disableBypassPermissionsMode": "disable",
    "disableAutoMode": "disable"
  }
}

disableBypassPermissionsMode はどのスコープに書いても効くので、「自分自身を Bypass モードから締め出す」目的で個人が書いても構いません。

スコープに関する2つの落とし穴

公式ドキュメントに明記されている、はまりやすいポイントを先に挙げておきます。

  1. defaultMode の auto と bypassPermissions は、プロジェクト設定・ローカル設定からは効きません。 ユーザー設定かマネージド設定に書くか、--permission-mode フラグで指定します。プロジェクトの settings.json に "defaultMode": "auto" と書いても Manual モードで起動して「あれ?」となるのは、この仕様が原因です。
  2. プロジェクト設定の allow ルールは、そのフォルダを「信頼」するまで効きません。 リポジトリをクローンした直後は信頼ダイアログが出るので、そこで承認します。deny と ask は制限を強める方向なので、信頼前から効きます。

レイヤー1:CLAUDE.md とルールファイルで「前提」を伝える

CLAUDE.md は「エージェント向けの README」

README.md が人間向けの説明書だとすれば、CLAUDE.md はコーディングエージェント向けの説明書です。Markdown で自然言語で書き、Claude Code はセッション開始時にこれを自動で読み込みます。毎回プロンプトで説明していたことを、ここに移すわけです。

CLAUDE.md は「Claude Code にプロジェクトをどう理解させるか」を担い、後述の settings.json は「Claude Code 自身をどう動かすか」を担います。この役割分担を意識すると、どちらに何を書くか迷わなくなります。

/init でたたき台を作る

ゼロから書くのは大変なので、まずは /init コマンドで生成させるのが近道です。Claude Code が pyproject.toml や README.md、.gitignore、既存の .cursor/rules/ や .github/copilot-instructions.md などを読み、ビルドコマンドやテスト手順、規約を推測して CLAUDE.md を作ってくれます。すでに CLAUDE.md があれば、上書きではなく改善提案になります。

/init 日本語で生成して

生成される内容は既定では英語なので、上のように言語を指定すると日本語になります。

環境変数 CLAUDE_CODE_NEW_INIT=1 を付けて起動すると、対話形式の /init になります。「CLAUDE.md だけ作るか」「スキルやフックも一緒にセットアップするか」を質問され、サブエージェントがコードベースを調査したうえで、書き込む前にレビュー可能な提案が出てきます。

CLAUDE_CODE_NEW_INIT=1 claude

書くべきこと、書かないこと

CLAUDE.md は毎セッション、コンテキストウィンドウに読み込まれます。つまり書けば書くほどトークンを消費し、しかも長いほど指示が守られにくくなります。公式ドキュメントは1ファイルあたり200行以内を目安にしています。

書くべき内容 書くべきでない内容
ビルド・テスト・Lint のコマンド コードを読めば分かること(ディレクトリ一覧、依存パッケージ一覧)
プロジェクト固有の規約(「型は app/schemas/ に置く」) 一般的なアドバイス(「きれいなコードを書く」)
アーキテクチャ上の決定と、その理由 頻繁に変わる情報(今週のタスク、担当者)
「必ず X する」「絶対に Y しない」というルール 秘密情報(API キー、接続文字列)

判断の目安として、公式ドキュメントは「CLAUDE.md に追加すべきタイミング」を次のように示しています。同じ間違いを2回したとき、コードレビューで「知っていてほしかった」指摘が出たとき、前回と同じ訂正をチャットに打ったとき、そして新しいメンバーにも同じ説明が必要だと感じたときです。

指示は「検証できるくらい具体的に」書きます。「フォーマットを整える」ではなく「インデントは4スペース」、「テストしてから」ではなく「コミット前に uv run pytest を実行する」です。

shiftboard-api の CLAUDE.md

以上を踏まえて、題材プロジェクトの CLAUDE.md を書くと次のようになります。

# shiftboard-api

店舗スタッフのシフトを管理する REST API。

## 開発コマンド
- 依存関係のインストール: `uv sync`
- 開発サーバー: `uv run fastapi dev app/main.py`
- テスト: `uv run pytest`(DB を使うテストは `docker compose up -d db` が前提)
- Lint / フォーマット: `uv run ruff check . && uv run ruff format .`
- マイグレーション作成: `uv run alembic revision --autogenerate -m "<message>"`

## 規約
- Pydantic スキーマは `app/schemas/`、SQLAlchemy モデルは `app/models/` に置く
- ルーター(`app/api/`)にビジネスロジックを書かない。`app/services/` に切り出す
- 日時は必ずタイムゾーン付き(`datetime` の `tzinfo` あり)で扱う
- 新しいエンドポイントには必ず `tests/api/` にテストを追加する

## やらないこと
- `alembic downgrade` は実行しない(共有 DB を巻き戻すため)
- 本番相当の `.env` は読まない。設定値が必要なら `.env.example` を参照する

「やらないこと」を書いていますが、繰り返しになりますがこれは強制ではありません。後のレイヤーで同じ内容を deny とフックにも書きます。CLAUDE.md に書く意味は、Claude が「なぜ止められるのか」を事前に知っていることで、ブロックされたときに無駄な試行錯誤をせず、別案を探せる点にあります。

他ファイルの取り込みと AGENTS.md

CLAUDE.md の中で @path/to/file と書くと、そのファイルの内容が取り込まれます(バッククォートで囲んだ `@README` は取り込まれません)。

GitHub Copilot や Cursor など複数のツールで共通に使う AGENTS.md を運用しているチームも多いでしょう。Claude Code は AGENTS.md を自動では読みませんが、CLAUDE.md からインポートすれば重複なく共有できます。

@AGENTS.md

## Claude Code 向けの追記
- `app/services/` 以下の変更は plan モードで計画してから着手する

ルールファイルで「該当ファイルを触るときだけ」読ませる

CLAUDE.md が肥大化してきたら、.claude/rules/ ディレクトリにトピック別の Markdown を置きます。ここで便利なのが、YAML フロントマターの paths で適用対象を絞れる点です。paths があるルールは、Claude がそのパターンに一致するファイルを読んだときにだけ読み込まれるので、コンテキストを節約できます。

---
paths:
  - "app/api/**/*.py"
---

# API ルーターの規約

- エンドポイント関数には `response_model` を必ず指定する
- 例外は `HTTPException` を直接 raise せず、`app/api/errors.py` のヘルパーを使う
- クエリパラメータの検証は Pydantic モデルで行い、関数内で手書きしない
---
paths:
  - "tests/**/*.py"
---

# テストの規約

- 振る舞いを変える前に、失敗するテストを先に書く
- DB を使うテストは `db_session` フィクスチャを使い、テスト間でデータを共有しない
- `time.sleep` を使わず、`freezegun` で時刻を固定する

ディレクトリ構成は次のようになります。

shiftboard-api/
├── .claude/
│   ├── CLAUDE.md
│   └── rules/
│       ├── api.md        # app/api/ を触るときだけ読まれる
│       └── testing.md    # tests/ を触るときだけ読まれる

paths を書かないルールは CLAUDE.md と同じ扱いで起動時に読み込まれます。また ~/.claude/rules/ に置けば、自分の全プロジェクトに効く個人ルールになります。

自動メモリ:Claude が自分で書くメモ

もう1つ、Claude が自分で書くメモリがあります。作業中に「これは次回も役立つ」と判断した内容が、~/.claude/projects/<project>/memory/ 配下の MEMORY.md に記録され、次のセッションの冒頭で読み込まれます(先頭200行または25KBまで)。

記録されるのは「あなたの役割や好み」「あなたが与えた訂正」「コードから読み取れないプロジェクトの経緯」「Issue トラッカーの場所」といった種類の情報で、コードを読めば分かる内容や、CLAUDE.md にすでに書いてあることは記録しない設計です。

/memory コマンドで内容を確認・編集できるほか、トグルでオン・オフを切り替えられます。プロジェクト単位で無効にしたい場合は settings.json に次を書きます。

{
  "autoMemoryEnabled": false
}

読み込まれたか確認する

「CLAUDE.md を書いたのに守ってくれない」というとき、まず疑うべきは「そもそも読み込まれているか」です。セッション中に /context を実行すると、Memory files の欄に読み込まれたファイルの一覧が出ます。ここに無ければ、置き場所かファイル名が間違っています。


レイヤー2:settings.json の permissions で「許可・確認・禁止」を決める

settings.json とは

settings.json は Claude Code 自身の動作を決める JSON 形式の設定ファイルです。権限、フック、サンドボックス、モデル、環境変数、UI など幅広い項目を設定できます。この記事で扱うのは主に permissions と hooks です。

書き方で1つ注意があります。settings.json は厳密な JSONです。// コメントや末尾のカンマがあると読み込みエラーになり、次回起動時に Settings Error のダイアログが出ます。エディタで補完と検証を効かせるために、先頭に $schema を書いておくと安心です。

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json"
}

/config で変えられるもの、直接編集が必要なもの

Claude Code の中で /config を実行すると、テーマや出力スタイル、既定の権限モードなど、よく使う個人向けの項目をメニューから変更できます。変更内容は主に ~/.claude/settings.json に書き出されます。/config verbose=true のように key=value で1項目だけ変えることもできます。

ただし /config に出てくるのは一部の項目だけです。permissions の細かいルールや hooks は、ファイルを直接編集する(または Claude Code に編集させる)ことになります。

なお、Claude Code は settings.json の変更を監視していて、permissions や hooks の編集は再起動なしに実行中のセッションへ反映されます。編集のたびに claude を立ち上げ直す必要はありません。

allow / ask / deny の3つのリスト

ツール権限は permissions オブジェクトの3つの配列で宣言します。

  • allow: 確認なしで実行してよい操作
  • ask: 実行のたびに人間に確認する操作
  • deny: 常にブロックする操作

判定順序は deny → ask → allow で、最初に一致したルールが適用されます。ルールの細かさは順序に影響しません。Bash(git push *) を ask に、Bash(git push origin feature/*) を allow に書いても、ask のほうが先に評価されるので確認が出ます。

ツール呼び出しがdeny、ask、allowの順に評価され、どれにも一致しない場合は動作モードの既定動作に従う流れ

図5: 先に deny を見るため、allow と deny の両方に書いた操作は必ず止まる(静止画版: fig05_permission_flow.png)

どのリストにも一致しない操作は、そのときの動作モード(後述)の既定動作に従います。Manual モードなら人間に確認、Auto モードなら分類器が判定、dontAsk なら自動で拒否、bypassPermissions なら実行です。

ルールの書き方

ルールは ツール名 または ツール名(指定子) の形式です。

書き方 意味 例
Bash Bash ツールのすべての呼び出し deny に書くと Bash ツール自体が Claude から見えなくなる
Bash(uv run pytest) 完全一致 uv run pytest -q にはマッチしない
Bash(uv run pytest *) * は任意の文字列(スペース含む) uv run pytest -q tests/api にマッチ。末尾が * なら引数なしの uv run pytest にもマッチ
Read(./.env) ファイル読み取り Read(./.env.*) で .env.local などもカバー
Edit(/app/models/**) ファイル編集(/ 始まりは設定ファイルの基準ディレクトリからの相対) Write や MultiEdit ではなく Edit で書く
WebFetch(domain:docs.python.org) 取得先ドメイン サブドメインは domain:*.python.org
Agent(model:opus) 入力パラメーターの値(deny / ask のみ) Opus 指定のサブエージェント起動を拒否
mcp__github__* MCP サーバーのツール mcp__github で全ツール、mcp__github__get_* で一部

Bash のワイルドカードには、公式ドキュメントが特に注意を促している癖が2つあります。

  1. 末尾 * の前のスペースは意味を持つ。 Bash(ls *) は「ls の直後にスペースか行末」にマッチするので lsof は含まれません。一方 Bash(ls*) はスペースなしなので lsof にもマッチしてしまいます。
  2. * はサブコマンドの後ろに置く。 Bash(git *) はすべての git コマンドを許可します。Bash(git * main) のように * をサブコマンドの位置に置くと、git push origin main も git -c core.fsmonitor=<script> diff main も通ってしまうため、起動時に警告が出ます。

もう1つ、複合コマンドは分割して評価されることも知っておく必要があります。&&、||、;、| などでつながれたコマンドは1本として扱われず、allow ルールは各サブコマンドすべてに一致しなければ自動承認されません。逆に deny と ask は「どれか1つのサブコマンドが一致すれば」適用され、サブシェルや $(...) の中まで見ます。

Read の deny はどこまで効くか

Read(./.env) を deny に書くと、組み込みの Read ツールだけでなく、Bash 内の cat .env や head .env のような Claude Code が認識できるファイル読み取りコマンド、さらに < .env のようなリダイレクトもブロックされます。ただし、python -c "open('.env').read()" のようにプロセスの中でファイルを開くケースや、grep -r pattern . のようにファイル名を指定しない読み取りには効きません。プロセス単位で確実に塞ぎたい場合は、レイヤー4のサンドボックスと併用します。

shiftboard-api の権限設定

チームで共有するプロジェクト設定 <repo>/.claude/settings.json に、次のように書きます。

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "defaultMode": "plan",
    "allow": [
      "Bash(uv run pytest *)",
      "Bash(uv run ruff check *)",
      "Bash(uv run ruff format *)",
      "Bash(uv run mypy *)",
      "Bash(docker compose up -d db)",
      "Bash(docker compose ps *)"
    ],
    "ask": [
      "Bash(uv add *)",
      "Bash(uv remove *)",
      "Bash(uv run alembic *)",
      "Bash(docker compose *)",
      "Bash(git push *)"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Bash(sudo *)",
      "Bash(git push --force *)",
      "Bash(git push -f *)",
      "Bash(docker compose down -v *)"
    ]
  }
}

各キーの意図は次のとおりです。

  • defaultMode: plan — セッション開始時は計画モードにし、いきなり編集させない。plan はプロジェクト設定からでも有効です
  • allow — テスト・Lint・型チェックと、ローカル DB の起動確認は「完了条件を満たしているか」を Claude 自身が回すために必要なので確認なしにする
  • ask — 依存の追加・削除、マイグレーション、Docker、push は影響範囲が広いので、その都度立ち止まる
  • deny — 秘密ファイルの読み取り、sudo、force push、ボリューム削除は原則として実行させない。docker compose * を ask に書いていても、down -v は deny が先に評価されるので必ず止まります

.env.example は読めるようにしておきたいので、Read(./.env) と Read(./.env.*) を分けて書いています(Read(./.env*) と書くと .env.example も塞いでしまいます)。

動作を確認する

設定できたら、/permissions でルールが読み込まれているかを確認します。この画面は、各ルールがどの settings.json 由来かを表示してくれるので、「ユーザー設定とプロジェクト設定のどちらに書いたか」を忘れたときにも役立ちます。ルールの追加・削除もここから行え、変更は Claude の次のツール呼び出しから反映されます。

次に、ask が効いていることを試します。「マイグレーションを最新まで適用して」と指示すると、uv run alembic upgrade head の直前で確認プロンプトが出ます。

askルールにより、alembic upgradeコマンドの実行前に確認プロンプトが表示される画面イメージ

画面イメージ(再現図): ask に一致したコマンドは、実行前にユーザーの承認を待つ

このプロンプトで「Yes, and don’t ask again」を選ぶと、そのコマンドの allow ルールが .claude/settings.local.json に保存され、次回から確認されなくなります。ただし ask ルールはローカルの allow より先に評価されるので、プロジェクト設定に ask がある限り確認は出続けます。 これは意図された動作で、チームの「ここは必ず確認」という判断を個人が黙って無効化できない仕組みです。

deny のほうは、.env を作って「.env の中身を教えて」と頼んでみると分かります。Claude Code は読み取りをブロックし、Claude はその旨を報告して、.env.example を代わりに参照する、といった別案を提示するはずです。

動作モード:確認を「誰が」するのか

permissions のルールに一致しない操作をどう扱うかは、動作モード(permission mode)で決まります。セッション中に Shift+Tab で切り替えられ、defaultMode で起動時のモードを指定できます。

モード 確認なしで実行できるもの 向いている場面
default(Manual) 読み取りのみ。編集・シェル・ネットワークは初回に確認 慎重に進めたい作業、初めて触るコード
acceptEdits 読み取り+作業ディレクトリ内のファイル編集と mkdir・mv などの基本コマンド 編集後に git diff でまとめてレビューする
plan 読み取りと調査のみ。計画を承認するまで編集しない 変更前にコードベースを調べさせる
auto 分類器が安全と判定した操作すべて 長いタスク、確認疲れの解消
dontAsk 事前承認済みのツールのみ。確認が必要な操作は自動で拒否 CI やスクリプトからの実行
bypassPermissions すべて(deny と一部の保護対象を除く) ネットワークから隔離したコンテナや VM の中だけ

ここで重要な最新情報があります。2026年8月以降、Pro / Max / Team プランでは auto が既定の起動モードになりました(Enterprise プランや API キー経由、Bedrock などのサードパーティプロバイダーでは引き続き Manual が既定です)。書籍やブログの多くは Auto モードを「Research preview の新機能」として紹介していますが、いまや「何も設定しなければ Auto で始まる」状態です。Auto モードの中身は後の節で詳しく見ます。

bypassPermissions(--dangerously-skip-permissions)は、権限プロンプトと安全チェックを丸ごと飛ばすモードです。公式ドキュメントは「隔離されたコンテナや VM の中でのみ使う」ことを繰り返し強調しています。deny ルールと、後述のフックだけは、このモードでも効きます。


レイヤー3:フックで「必ず実行させる/必ず止める」

フックとは

フック(Hooks)は、セッション開始やツール実行といった特定のタイミングで自動実行される処理です。シェルコマンドのほか、HTTP エンドポイント、MCP ツール、LLM への単発プロンプト、サブエージェントを実行できます。

権限ルールが「あらかじめ書いたパターンに一致するか」で判定するのに対し、フックは自分で書いたスクリプトがツールの入力を丸ごと受け取り、その場で判断できます。「編集されたのが .py なら Lint とテストを走らせる」「コマンド文字列のどこかに rm -rf が含まれていたら止める」といった、パターンだけでは書けない制御ができるのが強みです。

フックが発火するタイミング

SessionStart、UserPromptSubmit、PreToolUse、ツール実行、PostToolUse、Stop、SessionEndの流れと、ツール呼び出しごとのループを示す図

図6: 「実行前に止める」は PreToolUse、「実行後に検証する」は PostToolUse、「終了通知」は Stop(静止画版: fig06_hook_lifecycle.png)

フックのイベントは30を超えますが、まず押さえるべきは次の7つです。

イベント 発火するタイミング ブロックできるか
SessionStart セッションの開始・再開時 不可
UserPromptSubmit プロンプト送信直後、Claude が処理する前 可(exit 2 でプロンプトを破棄)
PreToolUse ツール呼び出しの実行前 可(ツール実行をブロック)
PostToolUse ツール呼び出しの成功後 不可(実行済み。stderr は Claude に渡る)
Notification Claude Code が通知を出すとき 不可
Stop Claude が応答を終えたとき 可(応答終了を阻止して続行させる)
SessionEnd セッション終了時 不可

図6の点線で囲んだ部分が「エージェント実行ループ」で、Claude Code がツールを呼ぶたびに PreToolUse → ツール実行 → PostToolUse が繰り返されます。ツールの入力をチェックしたり、実行結果を検証したりする処理は、ここに差し込みます。

このほかにも PermissionRequest(権限判断が必要なとき)、SubagentStop(サブエージェント終了時)、PreCompact(コンテキスト圧縮前)、ConfigChange(設定ファイル変更時)など、多数のイベントがあります。アップデートのたびに増えるので、正確な一覧は公式の Hooks reference を参照してください。

設定の3層構造

フックは settings.json の hooks オブジェクトに、次の3層で記述します。

  1. イベント: いつ実行するか(例: PreToolUse)
  2. マッチャー: どの条件で絞るか(例: Bash、Edit|Write)
  3. ハンドラ: 何を実行するか(例: command)
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-dangerous.sh",
            "args": []
          }
        ]
      }
    ]
  }
}

マッチャーはツール名などに対するフィルターです。Bash は Bash ツールだけ、Edit|Write はどちらかに完全一致、mcp__github__.* は github サーバーの全ツールにマッチします。省略するか "*" にすると、そのイベントすべてで発火します。文字・数字・_・-・|・, だけで書くと完全一致、それ以外の文字を含むと正規表現として解釈されます。

ハンドラには5種類あります。

type 内容 用途の例
command シェルコマンドを実行。stdin で JSON を受け取り、終了コードと stdout で結果を返す Lint 実行、ファイルチェック、通知
http 指定 URL に JSON を POST。レスポンス JSON で結果を返す 外部サービス連携、Webhook
mcp_tool 接続済み MCP サーバーのツールを直接呼ぶ セキュリティスキャン、外部への通知
prompt 単発の LLM 評価。モデルが許可・拒否を JSON で返す コマンドの安全性判定
agent サブエージェントが Read / Grep などで検証してから判断(実験的) テスト結果の検証、コード品質チェック

最もよく使うのは command です。${CLAUDE_PROJECT_DIR} はセッションを開始したプロジェクトのルートに置き換わるので、スクリプトを .claude/hooks/ に置いて Git で共有できます。args を付けると「exec 形式」になり、シェルを介さずにスクリプトを直接起動するため、パスに空白が含まれていてもクォートを気にしなくて済みます。

command ハンドラの終了コード

command ハンドラの結果は終了コードで制御します。ここが最もつまずきやすいポイントです。

終了コード 意味 動作
0 成功 stdout に JSON があれば解釈される。多くのイベントではトランスクリプトに表示されず、デバッグログに記録
2 ブロック ツール実行やプロンプト処理を止め、stderr の内容を Claude にフィードバックする
その他(1 を含む) 非ブロックエラー 処理は続行する。 トランスクリプトにエラー通知と stderr の先頭行が表示される

つまり、exit 1 では止まりません。Unix の慣習では失敗は 1 ですが、Claude Code のフックで「止める」意味を持つのは 2 だけです。ポリシーを強制するフックは必ず exit 2 で終わらせてください。

終了コードだけでなく、stdout に JSON を出力して細かく制御することもできます。PreToolUse なら次の形で、許可・拒否・ユーザー確認を指定できます。

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "共有 DB を巻き戻すため alembic downgrade は禁止です"
  }
}

permissionDecision には "allow"、"deny"、"ask" を指定できます。"deny" の場合、理由は Claude に渡り、Claude はそれを踏まえて別の手段を考えます。

ここが権限ルールより強い点

公式ドキュメントに、フックと権限ルールの関係が明記されています。

  • exit 2 でブロックするフックは、allow ルールより先に効きます。「Bash を allow に入れて基本は素通しにしつつ、特定コマンドだけフックで弾く」という構成が可能です
  • 逆に、フックが "allow" を返しても、deny や ask のルールは無視されません。deny は常に最優先です
  • PreToolUse フックは bypassPermissions モードでも走るので、権限プロンプトが素通しでもフックで止まる「二段構え」になります

権限ルールでは Bash(rm *) を deny にしても /bin/rm -rf や sh -c 'rm -rf ...' は止められませんが、フックはコマンド文字列全体を受け取るので、こうした書き方も検出できます。とはいえ、変数展開やエスケープで迂回する余地は残るため、「フックも完璧な境界ではない」と理解しておき、確実性が必要なところはサンドボックスで補います。

ハンズオン1:作業完了をデスクトップ通知する

まず、いちばん簡単で効果を実感しやすいフックから始めます。Claude が応答を終えた(Stop)ときに OS の通知を出すもので、長いタスクを任せて別の作業をしているときに便利です。

これは個人の好みなので、ユーザースコープの ~/.claude/settings.json に書きます。

macOS

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code が応答を完了しました\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

Linux(notify-send が使える環境)

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "notify-send 'Claude Code' '応答を完了しました'"
          }
        ]
      }
    ]
  }
}

Stop はマッチャーに対応していないので、matcher は書きません。自分で JSON を書くのが面倒なら、Claude Code にこう頼むのが早道です。

Claude Code が応答を完了したときに OS の通知を出す Stop フックを、
ユーザー設定の ~/.claude/settings.json に追加してください

Claude Code は .claude/ 配下への書き込みを「保護パス」として扱うため、settings.json を編集する前に確認プロンプトが出ます。内容を確認して承認してください。動作確認は、何か短い指示(例: 「この README を要約して」)を出して、応答が終わった瞬間に通知が来れば成功です。

通知が出ない場合、macOS では osascript の通知が「スクリプトエディタ」経由で送られるため、システム設定の「通知」でスクリプトエディタからの通知が許可されているかを確認します。一度ターミナルで osascript -e 'display notification "test"' を実行すると、一覧にスクリプトエディタが現れます。音も鳴らしたければ、コマンドの末尾に ; afplay /System/Library/Sounds/Glass.aiff を足します。

なお、フックはターミナルを直接制御できない(/dev/tty が使えない)ため、ターミナル自体の通知機能を使いたい場合は、stdout に {"terminalSequence": "..."} という JSON を返す方法が用意されています。iTerm2 や Ghostty などで使えるエスケープシーケンスを Claude Code が代わりに出力してくれます。

ハンズオン2:編集後に自動で Lint とテストを回す

次は、Claude が Python ファイルを編集するたびに ruff と pytest を走らせ、失敗したらその内容を Claude に返す PostToolUse フックです。これがあると、「テストを通してから完了と報告して」といちいち書かなくても、Claude が自分で修正ループを回してくれます。

まず、スクリプトを <repo>/.claude/hooks/check-after-edit.sh として作ります。

#!/usr/bin/env bash
# .claude/hooks/check-after-edit.sh
# PostToolUse(Edit|Write) から呼ばれる。編集されたファイルが .py のときだけ ruff と pytest を実行する。
set -u

input="$(cat)"
changed_file="$(jq -r '.tool_input.file_path // empty' <<<"$input")"

# Python 以外(pyproject.toml や README.md など)の編集ではスキップ
case "$changed_file" in
  *.py) ;;
  *) exit 0 ;;
esac

cd "${CLAUDE_PROJECT_DIR:-.}" || exit 0

# Lint は編集したファイルだけ。失敗したら先頭20行を stderr に出して exit 2
lint_out="$(uv run ruff check "$changed_file" 2>&1)" || {
  echo "ruff check failed for $changed_file:" >&2
  echo "$lint_out" | head -20 >&2
  exit 2   # PostToolUse では実行は止まらないが、stderr が Claude に渡る
}

# テストは -q -x で軽量に全体を回す。失敗したら末尾30行を stderr に出して exit 2
test_out="$(uv run pytest -q -x 2>&1)" || {
  echo "pytest failed after editing $changed_file. 以下の出力を確認して修正してください。" >&2
  echo "$test_out" | tail -30 >&2
  exit 2
}

exit 0

ポイントは次の3点です。

  1. Claude Code はフックの標準入力に、ツール呼び出しの内容を JSON で流し込みます。jq で .tool_input.file_path を取り出し、拡張子で実行の要否を判断しています。jq が未インストールなら brew install jq などで入れておいてください
  2. PostToolUse では実行はすでに終わっているので exit 2 でも巻き戻せませんが、stderr の内容が Claude にフィードバックされます。exit 0 で終えると stderr はデバッグログに行くだけで Claude には見えないため、失敗を伝えたいときは exit 2 を使います
  3. 出力はいったん変数に受けてから head / tail で絞っています。uv run ruff ... | head のようにパイプでつなぐと、シェルはパイプ末尾の head の終了コードしか見ないため、Lint やテストの失敗を検知できません。フックの出力は10,000文字を超えるとファイルに退避されるため、要点だけ返すほうが Claude も読みやすくなります

ハンズオン3:危険コマンドを実行前にブロックする

3つ目は、この記事の本丸である PreToolUse フックです。rm -rf、alembic downgrade、git reset --hard、git push --force のような取り返しのつかない操作を、実行される瞬間に止めます。

PreToolUseイベントの発火からmatcher、if条件、ハンドラ実行、exit 2によるブロックまでの流れ

図7: matcher(ツール名)→ if(コマンドの形)→ スクリプト、と絞り込んでから判定する(静止画版: fig07_hook_resolution.png)

スクリプトを <repo>/.claude/hooks/guard-dangerous.sh として作ります。

#!/usr/bin/env bash
# .claude/hooks/guard-dangerous.sh
# PreToolUse(Bash) から呼ばれる。コマンド文字列全体を検査し、危険な操作を JSON の deny で止める。
set -u

command="$(jq -r '.tool_input.command // empty')"

# 検出パターン(正規表現)。必要に応じて追加する。
patterns=(
  'rm[[:space:]]+(-[a-zA-Z]+[[:space:]]+)*-[a-zA-Z]*(r[a-zA-Z]*f|f[a-zA-Z]*r)'  # rm -rf, rm -fr, rm -v -rf
  'rm[[:space:]]+(-[a-zA-Z]+[[:space:]]+)*-r[[:space:]]+(-[a-zA-Z]+[[:space:]]+)*-f' # rm -r -f
  'rm[[:space:]]+.*--recursive'              # rm --recursive --force
  'alembic[[:space:]]+downgrade'             # 共有 DB の巻き戻し
  'git[[:space:]]+push[[:space:]].*(--force|-f)([[:space:]]|$)'
  'git[[:space:]]+reset[[:space:]]+--hard'
  'docker[[:space:]]+compose[[:space:]]+down.*-v'
  '(DROP|drop)[[:space:]]+(TABLE|table|DATABASE|database)'
)

for p in "${patterns[@]}"; do
  if grep -Eq "$p" <<<"$command"; then
    jq -n --arg reason "Blocked by guard: '$command' は危険な操作として禁止されています。個別ファイルの削除や alembic upgrade など、影響範囲の狭い手段を検討してください。" '{
      hookSpecificOutput: {
        hookEventName: "PreToolUse",
        permissionDecision: "deny",
        permissionDecisionReason: $reason
      }
    }'
    exit 0
  fi
done

exit 0   # 判定なし。通常の権限フローに進む

ここでは exit 2 ではなく、JSON で permissionDecision: "deny" を返しています。どちらでもブロックできますが、JSON のほうが「理由」を構造化して渡せるので、Claude が次の一手を考えやすくなります。exit 0 で JSON を返すのが正しい組み合わせで、exit 2 と JSON を混ぜると exit 2 のブロックが優先されます。

パターンは正規表現で書いているので、rm -rf だけでなく rm -fr や rm -r -f、/bin/rm -rf、sh -c 'rm -rf ...' のような書き方も検出できます。手元で echo '{"tool_input":{"command":"rm -rf build"}}' | .claude/hooks/guard-dangerous.sh のようにサンプル JSON を流し込めば、Claude Code を起動しなくても動作を確認できます。

なお、このガードは文字列一致なので意図的に保守的です。たとえば echo "drop table のドキュメントを整形" のような無害なコマンドも DROP TABLE のパターンに引っかかります。その場合も Claude には理由が渡るので、別の書き方で先に進みます。誤検知が気になる箇所はパターンを絞り込んでください。

3つのフックを settings.json にまとめる

ハンズオン2と3はチームで共有したいので、プロジェクト設定 <repo>/.claude/settings.json に追加します。前節の permissions はそのまま残し、hooks キーを足す形です。

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "defaultMode": "plan",
    "allow": ["Bash(uv run pytest *)", "Bash(uv run ruff check *)", "Bash(uv run ruff format *)", "Bash(uv run mypy *)", "Bash(docker compose up -d db)", "Bash(docker compose ps *)"],
    "ask": ["Bash(uv add *)", "Bash(uv remove *)", "Bash(uv run alembic *)", "Bash(docker compose *)", "Bash(git push *)"],
    "deny": ["Read(./.env)", "Read(./.env.*)", "Read(./secrets/**)", "Bash(sudo *)", "Bash(git push --force *)", "Bash(git push -f *)", "Bash(docker compose down -v *)"]
  },
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-after-edit.sh",
            "args": [],
            "timeout": 180
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-dangerous.sh",
            "args": []
          }
        ]
      }
    ]
  }
}

timeout は秒単位で、command ハンドラの既定は600秒です。テストが長いプロジェクトでは、ここを調整してください。なお、タイムアウトしたフックは「判定なし」扱いで、ツール実行は止まりません。止めたい処理を、時間のかかる処理と同じスクリプトに入れないのがコツです。

最後に、スクリプトに実行権限を付けます。これを忘れるとフックは起動しませんが、エラーは「非ブロックエラー」として小さく表示されるだけで、ツール実行は素通りします。初回はトランスクリプトに hook error の通知が出ていないか、必ず目で確認してください。

chmod +x .claude/hooks/*.sh

動作を確認する

まず /hooks を実行します。設定済みのフックがイベント別に一覧され、どの settings.json 由来かも表示されます(この画面は読み取り専用で、編集はファイルで行います)。

次に、Claude Code に app/services/ 配下の Python ファイルを編集させてみます。編集が完了した直後に PostToolUse フックが走り、ruff と pytest が実行されます。テストが失敗すれば、その内容が Claude に返り、修正が始まります。

Editツールの直後にPostToolUseフックがruffとpytestを実行し、失敗内容がClaudeに返って修正が始まる画面イメージ

画面イメージ(再現図): 編集 → 自動テスト → 失敗内容のフィードバック → 修正、というループが人手なしで回る

続いて「テスト用に作った一時ディレクトリを rm -rf で消して」のように危険な操作を指示してみます。PreToolUse フックがコマンドを実行前に検知してブロックし、Claude はその理由を受け取って別の手段(対象ファイルを個別に削除する、git clean -n で対象を確認するなど)を検討します。

rm -rfコマンドがPreToolUseフックにブロックされ、Claudeが個別削除に切り替える画面イメージ

画面イメージ(再現図): ブロックの理由が Claude に渡るため、止めるだけでなく次の行動を促せる

フックが期待どおりに動かないときは、claude --debug で起動すると、フックの標準出力・標準エラー出力がデバッグログに残ります。

フックを書くときのヒント

  • フックの設定自体を Claude Code に頼む。 「Bash で alembic downgrade を含むコマンドをブロックする PreToolUse フックを作って」のように相談しながら進めると、JSON の構造やスクリプトのひな形を正しく出してくれます。慣れないうちは、これが最も確実です
  • if フィールドで無駄な起動を減らす。 ハンドラに "if": "Bash(git *)" のように権限ルール構文で条件を書くと、一致したときだけスクリプトが起動します。ただし if の判定はベストエフォートなので、確実に止めたい判定はスクリプト側でも行います
  • additionalContext で情報を渡す。 ブロックする以外にも、PostToolUse の JSON で additionalContext を返すと、「このファイルは自動生成です。app/schemas/base.py を編集してください」のような補足を Claude に読ませられます
  • サブエージェントの中でも走る。 settings.json のフックはサブエージェントのツール呼び出しでも発火します。入力 JSON の agent_id で区別できます
  • 一時的に全部止める。 "disableAllHooks": true を設定するとフックを無効化できます。個別のフックだけ止める設定はないので、必要なら JSON から削除します

Auto モード:人の代わりに「分類器」が確認する

何が起きているのか

前述のとおり、Pro / Max / Team プランでは Auto モードが既定になりました。仕組みを知らないまま使うと「なぜこれが通って、なぜあれが止まるのか」が分からず不安になるので、判定の流れを押さえておきましょう。

Auto モードでは、コーディングエージェントである Claude 本体とは別に、分類器(Classifier)と呼ばれる専用モデルが、ツール呼び出しのたびにその危険性を評価します。既定では Claude Sonnet 5 が使われます。人間に確認する代わりに分類器が判定し、安全と判断された操作だけを実行する仕組みです。

Autoモードでは、まず明示ルール、次に読み取りや作業ディレクトリ内の編集、最後に分類器の判定という順で処理される流れ

図8: 人が確認する代わりに分類器が判定するが、明示ルールが常に先に評価される(静止画版: fig08_auto_mode.png)

判定の順序は次のとおりです。

  1. allow / ask / deny ルールに一致すれば即決。 ask は Auto モードでも必ず人間に確認します
  2. 読み取りと、作業ディレクトリ内のファイル編集は自動承認。 ただし .claude や .git などの「保護パス」への書き込みは分類器に回されます
  3. それ以外は分類器が判定。 ブロックされた場合、Claude はその理由を受け取り、別の手段を試します

分類器はユーザーのメッセージ、ツール呼び出し、CLAUDE.md の内容を見ますが、ツールの実行結果は見ません。Web ページやファイルに仕込まれた悪意ある指示(プロンプトインジェクション)で分類器を直接操作されないための設計です。この点でも、CLAUDE.md にプロジェクトの意図を書いておくことは分類器の判断材料になります。

既定で止まるもの、通るもの

分類器は「セッション開始時の作業ディレクトリと、その Git リモート」を信頼し、それ以外は外部として扱います。既定でブロックされる操作の代表例は次のとおりです。

  • curl | bash のようなコード取得と実行
  • 機密情報の外部エンドポイントへの送信
  • 本番デプロイ、本番 DB のマイグレーション
  • セッション前から存在したファイルの不可逆な削除
  • force push、git reset --hard、git clean -fd などの未コミット変更を捨てる操作
  • terraform destroy などインフラの破壊
  • クラウドストレージの一括削除、IAM 権限の付与

逆に、作業ディレクトリ内のファイル操作、マニフェストに宣言済みの依存のインストール、読み取り系の HTTP リクエスト、作業中リポジトリへの push などは既定で許可されます。

ルールの全文は、次のコマンドで JSON として出力できます。

claude auto-mode defaults

出力は非常に長いですが、allow / soft_deny / hard_deny / environment の4カテゴリに分かれています。soft_deny はユーザーの意図や allow ルールで覆せる一方、hard_deny は無条件でブロックされる、という違いがあります。組織として「このバケットや内部レジストリは信頼してよい」と伝えたい場合は、autoMode.environment を管理者が設定します。

Auto モードで知っておくべき挙動

  • 広すぎる allow は無効化される。 Auto モードに入ると、Bash(*) や Bash(python*) のような「任意コード実行を許す allow ルール」は一時的に外されます。Bash(uv run pytest *) のような狭いルールはそのまま効きます
  • ブロックが続くと人間に戻る。 3回連続、または合計20回ブロックされると Auto モードは一時停止し、通常の確認プロンプトに戻ります
  • 会話で伝えた境界も効く。 「push はしないで」「デプロイ前に私のレビューを待って」と伝えると、分類器はそれをブロック条件として扱います。ただしこれはルールとして保存されず、コンテキスト圧縮で消える可能性があるので、確実に守らせたいなら deny ルールに書きます
  • /permissions の Auto mode タブから、分類器のルールを確認できます。ブロックされた操作は Recently denied タブに残り、r キーで手動承認して再試行できます

公式ドキュメントは「Auto モードは確認プロンプトを減らすが、安全を保証するものではない」と明記しています。本番デプロイや不可逆なインフラ変更を伴う作業では、Auto モード単体に頼らず、deny ルールとフック、そしてサンドボックスを重ねてください。


レイヤー4:サンドボックスで「到達できる範囲」を物理的に絞る

権限ルールもフックも、最終的には「コマンド文字列をどう解釈するか」に依存しています。プロセスの中でファイルを開くスクリプトや、変数展開で組み立てたコマンドまでは追いきれません。そこを補うのがサンドボックスです。

Claude Code のサンドボックスは、Bash ツールとその子プロセスを OS の機能(macOS の Seatbelt、Linux の bubblewrap)で隔離し、書き込めるディレクトリと接続できるドメインを制限します。セッション中に /sandbox で有効化するか、settings.json で sandbox.enabled を true にします。

権限ルールとサンドボックスは補完関係にあり、公式ドキュメントは「プロンプトインジェクションで Claude の判断が迂回されても、サンドボックスの制限は残る」ことを防御の多層化の理由に挙げています。詳細は本記事の範囲を超えるので、公式の Configure the sandboxed Bash tool を参照してください。


トラブルシューティング:「設定したのに効かない」時の確認順

設定が意図どおりに動かないときは、闇雲にファイルを書き換えるより、次の順で切り分けると早く原因にたどり着けます。

設定が効かない場合に、/statusでの読み込み確認、上位スコープの上書き、トラストの承認、フックの実行権限の順で診断する流れ

図9: 「読み込まれているか」→「上書きされていないか」→「信頼済みか」→「フック自体の不備」の順に切り分ける(静止画版: fig09_troubleshoot.png)

症状 確認すること よくある原因と対処
設定全体が効かない /status の Setting sources に、そのファイルが載っているか JSON の構文エラー(コメント、末尾カンマ)。/doctor でエラー内容を確認する
defaultMode: auto が効かない どのスコープに書いたか プロジェクト・ローカル設定からは効かない。~/.claude/settings.json に移す
allow だけ効かず、確認が出る フォルダを信頼したか。ask ルールが上位にないか 信頼ダイアログを承認する。プロジェクトの ask はローカルの allow より優先される
deny が効かない ルールの書き方 Bash(rm *) は /bin/rm を止めない。フックかサンドボックスで補う
CLAUDE.md が守られない /context の Memory files に載っているか 置き場所・ファイル名を確認。載っていても長すぎると守られにくいので200行以内に
フックが動かない /hooks に載っているか。トランスクリプトに hook error が出ていないか chmod +x 忘れ、パスの誤り、jq 未インストール。claude --debug でログを見る
フックが止めてくれない 終了コード exit 1 は非ブロック。exit 2 に直す。タイムアウトも「判定なし」扱い

/status と /config は同じダイアログの別タブです。Setting sources の行は「どのファイルを読んだか」を示すもので、「どのキーがどのファイル由来か」までは分かりません。ルール単位で出所を知りたいときは /permissions と /hooks を使います。


まとめ:制御レイヤーの整理

最後に、この記事で作った設定を「どのレイヤーで何を守っているか」の観点で整理します。

守りたいこと レイヤー 設定
プロジェクトの前提とコマンドを毎回伝える 1. CLAUDE.md /init で生成し、規約と「やらないこと」を追記
API とテストの規約は該当ファイルを触るときだけ読ませる 1. ルールファイル .claude/rules/api.md、testing.md に paths を指定
テスト・Lint は確認なしで回す 2. permissions.allow Bash(uv run pytest *) など
依存追加・マイグレーション・push は立ち止まる 2. permissions.ask Bash(uv run alembic *) など
秘密ファイル・force push・ボリューム削除は禁止 2. permissions.deny Read(./.env)、Bash(git push --force *) など
編集後にテストを自動で回し、失敗を Claude に返す 3. PostToolUse フック check-after-edit.sh(exit 2 で stderr を返す)
rm -rf や alembic downgrade を実行前に止める 3. PreToolUse フック guard-dangerous.sh(JSON で deny)
作業完了を知る 3. Stop フック デスクトップ通知(ユーザー設定)
コマンド解釈をすり抜ける操作を物理的に防ぐ 4. サンドボックス /sandbox または sandbox.enabled

大切なのは「どれか1つで完璧にする」のではなく、上のレイヤーで意図を伝え、下のレイヤーで安全を担保することです。CLAUDE.md に「やらないこと」を書いた上で deny とフックにも同じ内容を入れておけば、Claude は止められた理由を理解し、次の一手を自分で探せます。

次のステップ

  • サンドボックスの設定: /sandbox の各モードと、許可ドメイン・ディレクトリの指定方法を公式ドキュメントで確認する
  • スキル(Skills): 常時読み込む CLAUDE.md とは違い、必要なときだけ読み込まれる手順書。「マイグレーション作成の手順」のような複数ステップの作業はスキルに移すと CLAUDE.md が痩せます
  • /goal: 完了条件を宣言して、満たすまで Claude に作業を続けさせる機能。Auto モードで任せる範囲を広げるほど、完了条件の明確化が重要になります
  • マネージド設定: チームや組織で配布する場合は、managed-settings.json の配布方法と allowManagedPermissionRulesOnly などのロック設定を確認する

参考リソース

すべて Anthropic 公式の Claude Code ドキュメント(2026年9月時点)です。