要約
Claude Code Hooks は、CLAUDE.md や Skills では実現できない「確実に実行される制御」を提供する仕組みである。CLAUDE.md に書いたルールは LLM の判断に依存するため 100% の遵守が保証されないが、Hooks はシステムレベルで動作し、ツール実行の前後やセッションのライフサイクルに合わせてシェルスクリプトや HTTP リクエストを必ず実行する。
本記事では Hooks の基本概念を理解した上で、異なるパターンを示す3つの実例をコード付きで紹介する。
対象読者: Claude Code を日常的に使っている開発者で、CLAUDE.md だけでは制御しきれない場面に課題を感じている方。
この記事を読むことで得られるメリット
この記事を読むことで以下のことが分かる:
- Hooks と CLAUDE.md / Skills の根本的な違いが分かる
- 「いつ Hooks を使うべきか」の判断基準が明確になる
- settings.json の書き方とイベント・Exit Code の仕組みを理解できる
- 3つの実例で Stop + agent、PreToolUse + command、Stop + command のパターンを把握できる
- フックのデバッグ方法とよくある間違いを把握できる
この記事を読むのにかかる時間
約15分
環境
- Claude Code 2.0.65+
- macOS / Linux(シェルスクリプトベース)
- jq(JSON パーサー)がインストール済みであること
Hooks とは何か — なぜ必要なのか
CLAUDE.md の限界:「お願い」は 100% 守られない
CLAUDE.md はセッション開始時に読み込まれる静的テキストであり、Claude にとっては「参考情報」に過ぎない。LLM が最終的にその指示に従うかどうかは確率的であり、100% の実行保証がない。
実際に報告されている失敗シナリオを挙げる。
- 長時間プロジェクトで複雑なデバッグチェーンがコンテキストウィンドウの大半を消費した場合、Claude がリンターを実行せずにファイルを書き込む(出典: Dotzlaw Consulting)
- CLAUDE.md に「.env ファイルを絶対に編集するな」と書いていたにもかかわらず、Claude が .env を読み込み、認証情報を env.example にコピーして GitHub にコミットした(出典: paddo.dev)
- CLAUDE.md に「pnpm を使って」と指定しても、コンテキストの圧縮後に
npm installを実行してしまう
これらの問題の根本原因は、CLAUDE.md の指示がコンテキスト圧縮(Compaction)時に要約・希薄化されることにある。長いセッションほどこのリスクは高まる。
Hooks の決定論的実行保証
公式ドキュメントでは、Hooks を「LLM に実行を選択させるのではなく、確実に特定のアクションが常に発生することを保証する決定論的制御を提供する」と定義している。
Hooks は LLM の推論チェーンの外、システムレベルで実行される。Claude がどれだけ「実行したい」と判断しても、Hooks による制御は回避できない。
4つのハンドラータイプ
Hooks には用途に応じた4つのタイプがある。
タイプ | 用途 | 説明 |
|---|---|---|
| シェルコマンド実行 | シェルスクリプトを実行し、exit code で許可/ブロックを制御する。最も基本的 |
| HTTP POST 送信 | イベントデータを外部 Webhook エンドポイントに POST する。Slack 通知等に使用 |
| 単一ターン LLM 判定 | Claude モデルにプロンプトを送り、 |
| サブエージェント起動 | ツール使用可能なサブエージェントを生成し、複数ターンの検証を行う。最も強力だがトークン消費大。 |
判断基準:「提案」なら CLAUDE.md、「ルール」なら Hooks
判断の目安は「それが提案なのかルールなのか」である。提案なら CLAUDE.md、絶対に破られてはならないルールなら Hooks を使う(出典: Medium - Mustafa Morbel)。
設定レイヤーの全体像
Claude Code には複数の設定レイヤーがあり、上にいくほど柔軟で気軽に使える(が、確実性は低い)、下にいくほど厳格で回避不能(が、設定コストが高い)。
CLAUDE.md
(提案・ガイドライン)
─────────────────────
Skills / Commands
(タスク特化の知識・手順)
─────────────────────
permissions.deny
(組み込みの拒否ルール)
─────────────────────
Hooks
(決定論的な強制ルール)レイヤー | 性質 | 確実性 |
|---|---|---|
CLAUDE.md | 確率的(参考情報) | 70〜90% |
Skills / Commands | 確率的(構造化) | 70〜90% |
permissions.deny | 決定論的(簡易) | 90%+ |
Hooks | 決定論的(完全) | 100% |
確実性を重視するなら、決定論的な Hooks に寄せた方が望む結果を得られる確率は当然高くなる。一方で、Hooks はシェルスクリプトの作成や設定のメンテナンスといった運用コストがかかるため、コストと確実性のバランスでどのレイヤーを使うかを判断するのが現実的である。
settings.json の書き方
設定ファイルの場所
Hooks は .claude/settings.json(または .claude/settings.local.json)に記述する。
ファイル | スコープ | Git 管理 |
|---|---|---|
| ユーザー全体 | 通常しない |
| プロジェクト全体 | チームで共有する場合 |
| 個人のローカル設定 | しない(.gitignore 推奨) |
基本構造
{
"hooks": {
"イベント名": [
{
"matcher": "ツール名の正規表現(省略で全マッチ)",
"hooks": [
{
"type": "command",
"command": "実行するシェルコマンド"
}
]
}
]
}
}以下の設定は「Bash ツール実行前に check.sh を実行する」という意味である。スクリプトが exit 2 を返せばブロックされる。
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{ "type": "command",
"command": "./check.sh" }]
}]
}
}Claude Code はイベント発火時にJSON コンテキスト(ツール名・引数等)を stdin でフックに渡す。フック内で jq を使って解析するのが基本パターンである。
主要なイベント
イベント | 発火タイミング | ブロック可能 |
|---|---|---|
| ツール実行前 | はい(exit 2 でブロック) |
| ツール実行成功後 | いいえ |
| ターンの終了時(Claude が応答テキストを出力し終わった時) | はい(exit 2 で継続を強制) |
| 通知発生時(権限確認、アイドル状態等) | いいえ |
| セッション開始/再開時 | いいえ |
exit code の意味
exit code | 意味 |
|---|---|
| 成功。ツール実行を許可 |
| エラー。ログに記録されるがブロックしない |
| ブロック。ツール実行を阻止し、Claude にブロック理由を伝える |
重要: ブロックするには
exit 2が必要。exit 1ではブロックされない。 これは最もよくある間違いの一つである。
3つの実例で理解する Hooks
Hooks の仕組みを3つの異なるパターンで理解する。
実例1: 目次と本文の整合性チェック(Stop + agent)
Stop イベントと agent ハンドラーの組み合わせ。Claude の作業完了時にサブ AI が自動で品質検証を行う。
仕組み:
- Claude が記事やドキュメントの編集を完了する
- Stop イベントが発火し、agent ハンドラーが起動
- サブエージェントが目次(アジェンダ)と本文の見出しを照合
- 不一致があれば Claude にフィードバックし、修正を促す
.claude/settings.json:
{
"hooks": {
"Stop": [{
"hooks": [{ "type": "agent",
"prompt": "目次・アジェンダに記載された項目が本文で全てカバーされているか検証してください。不一致があれば報告してください。$ARGUMENTS" }]
}]
}
}ポイント:
- agent タイプ = サブ AI が自動検証。シェルスクリプト不要で、自然言語でルールを記述できる
- Stop イベント = 作業完了時に発火。記事・スライド・ドキュメント等の完成品を検証するのに最適
- 目次と本文の見出し不一致を自動検出し、品質を担保する
実例2: 危険コマンドのブロック(PreToolUse + command)
PreToolUse イベントと command ハンドラーの組み合わせ。Claude が rm -rf /、git push --force main、git reset --hard などの破壊的コマンドを実行しようとした場合に、実行前にシステムレベルでブロックする。
仕組み:
- Claude が Bash ツールでコマンドを実行しようとする
- PreToolUse フックが発火し、コマンド文字列を正規表現でチェック
- 危険パターンに該当 →
exit 2でブロック - 該当しない →
exit 0で実行を許可
.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-dangerous-commands.sh"
}
]
}
]
}
}.claude/hooks/block-dangerous-commands.sh:
#!/bin/bash
INPUT=$(cat)
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
DANGEROUS_PATTERNS=(
'rm\s+-rf\s+/'
'git\s+push\s+(-f|--force)\s+(origin\s+)?main'
'git\s+reset\s+--hard'
'DROP\s+TABLE'
':\(\)\{ :\|:& \};:'
)
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if echo "$CMD" | grep -qE "$pattern"; then
echo "BLOCKED: Dangerous command detected: $CMD" >&2
exit 2
fi
done
exit 0パターン | ブロック対象 | 理由 |
|---|---|---|
|
| ルートからの再帰的強制削除 |
|
| main ブランチへのフォースプッシュ |
|
| コミットされていない変更の全破棄 |
| SQL の DROP TABLE | データベーステーブルの削除 |
| フォーク爆弾 | システムリソース枯渇攻撃 |
ポイント:
- command タイプ = シェルスクリプトで判定。
exit 2を返すと実行を阻止 - PreToolUse イベント = ツール実行前に発火するため、危険な操作を未然に防げる
permissions.denyでもコマンドの拒否は可能だが、パイプやマルチラインコマンドで回避されるケースがある(出典: DEV Community)。PreToolUse フックはコマンド文字列全体を解析できるため、より堅牢
危険コマンドブロックの実装パターンは Blake Crosley のチュートリアル や AI Hero の解説 も参考になる。
実例3: Markdown → PDF 自動生成(Stop + command)
Stop イベントと command ハンドラーの組み合わせ。Claude の作業完了時にビルド成果物を自動更新する。
仕組み:
- Claude が Markdown ファイル(スライド等)を編集する
- 作業完了時に Stop フックが発火
- スクリプトが変更されたスライドファイルを検出し、
slidev exportで PDF を自動生成 - 変更がなければスキップ
.claude/settings.json:
{
"hooks": {
"Stop": [{
"hooks": [{ "type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/export-pdf.sh",
"timeout": 120000 }]
}]
}
}.claude/hooks/export-pdf.sh:
#!/bin/bash
cd "$CLAUDE_PROJECT_DIR"
# 変更されたスライドファイルを検出
changed=$(git diff --name-only HEAD -- 'projects/*/slides.md')
[ -z "$changed" ] && exit 0
for slide in $changed; do
dir=$(dirname "$slide")
npx slidev export \
--output "$dir/slides-export.pdf" \
"$slide"
doneポイント:
- Markdown を編集 → Stop で自動 PDF 化。ドキュメント・レポート・提案書などあらゆる MD → 成果物変換に応用可能
timeout: 120000(120秒)で PDF 生成に十分な時間を確保git diffで変更されたファイルだけを対象にすることで、不要なビルドを回避
フックスクリプトには実行権限を付与しておくこと。
chmod +x .claude/hooks/*.shデバッグの基本
/hooks コマンドで設定一覧を確認
Claude Code のプロンプトで /hooks と入力すると、イベント別に設定済みフックの一覧を読み取り専用で閲覧できる。設定ミスの確認に便利である。
Ctrl+O で verbose モード切替
verbose モードを有効にすると、トランスクリプトにフック実行の進捗が表示される。
よくある間違い
1. exit 1 を使ってしまう(出典: DEV Community)
ブロックするには exit 2 が必要。exit 1 はエラーとしてログに記録されるが、ツール実行はブロックされない。
# NG: ブロックされない
exit 1
# OK: ブロックされる
exit 22. $HOME 等の環境変数がパス展開されない
JSON 設定ファイル内で $HOME を使うと、変数展開されずにそのまま文字列として渡される。フックがサイレントにロードされなくなる。
// NG: $HOME は展開されない
"command": "$HOME/.claude/hooks/my-hook.sh"
// OK: $CLAUDE_PROJECT_DIR を使う
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/my-hook.sh"$CLAUDE_PROJECT_DIR は Hooks 実行時に Claude Code が自動で設定する環境変数であり、正しく展開される。これが唯一の例外である。
まとめ
- Hooks は CLAUDE.md / Skills とは根本的に異なる決定論的制御を提供する
- 「提案」は CLAUDE.md、「絶対に破られてはならないルール」は Hooks に書く
- ブロックするには
exit 2を使う(exit 1ではブロックされない) - 3つの実例で異なるパターンを理解:
- 目次と本文の整合性チェック(Stop + agent)
- 危険コマンドのブロック(PreToolUse + command)
- Markdown → PDF 自動生成(Stop + command)
基礎編では、Chrome 拡張の自動ビルド、TDD の強制、API スモークテスト、Slack 通知、重複コード検出、Skill 自動選択など、日常開発を支えるより実践的なフックのレシピを紹介する。