このレッスンで学ぶこと
- Hooksとは何か、どのライフサイクルイベントで発火するか
settings.json内でのhooks設定の書き方とmatcherの仕組み- 基本的なHookを実際に実装し、動作させる
- Hookが正しく動いているかをデバッグする方法
前提条件
- Lesson 1〜3でMCPの基礎を理解していること(HooksはMCPとは独立した仕組みですが、組み合わせて使うことが多いため)
settings.jsonの基本的な構造(.claude/settings.json等)に触れたことがあること
Hooksとは何か
Claude Codeは、ツールを呼び出す・応答を返す・セッションを開始するといった特定のタイミング(ライフサイクルイベント)で動作します。Hooksは、そのタイミングに合わせて自分で指定したコマンドを自動実行する仕組みです。
たとえば「Bashツールでrm -rfが実行されそうになったら止める」「ファイルを保存したら自動的にフォーマッタを走らせる」「セッション開始時にGitのブランチ情報を自動的に伝える」といったことが、プロンプトでの指示なしに、確実に毎回実行されるようになります。
CLAUDE.mdに「コミット前にlintを実行してください」と書いても、Claude Codeがその指示を忘れたり、優先度を下げたりする可能性はゼロではありません。Hooksは「忘れる」という概念自体が存在しない、確定的に実行される自動化です。「必ず実行されてほしい処理」はCLAUDE.mdではなくHooksに任せるのが安全です。
Hooksのライフサイクル
Hooksが発火するタイミングは多数定義されていますが、実務でまず押さえておきたいのは以下の代表的なイベントです。
| イベント | 発火タイミング | 主な用途 |
|---|---|---|
SessionStart | セッション開始・再開時 | プロジェクトのコンテキスト(ブランチ名等)を自動注入する |
UserPromptSubmit | ユーザーがプロンプトを送信した直後 | プロンプトの内容チェック、追加コンテキストの注入 |
PreToolUse | ツール実行前 | 危険なコマンドのブロック、入力値の検証・書き換え |
PostToolUse | ツール実行成功後 | 自動フォーマット、自動テスト実行、ログ記録 |
Stop | Claude Codeの応答が完了した時 | 通知の送信、後処理の実行 |
SessionEnd | セッション終了時 | クリーンアップ処理 |
flowchart TD
A["セッション開始"] -->|"SessionStart"| B["ユーザーがプロンプト送信"]
B -->|"UserPromptSubmit"| C["Claude Codeが処理"]
C --> D{"ツールを呼ぶ?"}
D -->|Yes| E["PreToolUse"]
E --> F["ツール実行"]
F -->|"成功"| G["PostToolUse"]
F -->|"失敗"| H["PostToolUseFailure"]
G --> C
H --> C
D -->|No| I["応答完了"]
I -->|"Stop"| J["セッション終了"]
J -->|"SessionEnd"| K["完了"]
style E fill:#fde8e8,stroke:#c0392b
style G fill:#e8f4fd,stroke:#1a73e8PreToolUseはツール実行前に割り込めるため、危険な操作のブロックに向いています。一方PostToolUseは実行が完了した後に発火するため、ブロックはできず、結果に対する後処理(フォーマット、通知、ログ記録など)に向いています。この違いは設計上重要なので覚えておきましょう。
PostToolUseの時点では、すでにツールは実行済みです。ここでエラーを返しても「実行を未然に防ぐ」ことはできません。実行そのものを止めたい場合は、必ずPreToolUseを使う必要があります。
settings.jsonでのHooks設定
設定ファイルの置き場所
Hooksは複数の場所のsettings.jsonに書くことができ、スコープによって用途が異なります。
| ファイル | スコープ | 共有 |
|---|---|---|
~/.claude/settings.json | 全プロジェクト共通 | 個人のみ(マシンローカル) |
.claude/settings.json | プロジェクト単位 | チーム共有(git管理対象) |
.claude/settings.local.json | プロジェクト単位 | 個人のみ(gitignore対象) |
CLAUDE.mdの運用方針にもあるとおり、チームで共有すべきHookは.claude/settings.jsonに、個人的な実験用のHookは.claude/settings.local.jsonに置くのが基本です。
JSON構造の基本形
{
"hooks": {
"イベント名": [
{
"matcher": "対象を絞り込む条件",
"hooks": [
{
"type": "command",
"command": "実行するコマンド"
}
]
}
]
}
}3階層になっている理由は、「どのイベントで」「どの対象に」「何を実行するか」を分離して指定できるようにするためです。1つのmatcherに複数のhooksを紐づけることもできます。
matcherの書き方
matcherは主にツール名に対するフィルタです。PreToolUse・PostToolUseでは、対象はtool_nameになります。
| 書き方 | 意味 | 例 |
|---|---|---|
省略 または "*" | すべてのツールにマッチ | 全ツール共通の処理 |
| ツール名そのもの | 完全一致 | "Bash" |
|区切り | 複数ツールの一致 | "Edit|Write" |
| 正規表現 | パターンマッチ | "^Notebook"、"mcp__.*__write.*" |
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "echo 'ファイルが編集されました'"
}
]
}
]
}
}MCPツールにマッチさせたい場合は、mcp__<サーバー名>__<ツール名>という命名規則を踏まえて正規表現を書きます。Lesson 3で作ったpricing-toolsサーバーの全ツールにマッチさせるなら"mcp__pricing-tools__.*"のようになります。
実践: 基本的なHookを実装する
危険なBashコマンド(rm -rf等)を検知してブロックする、PreToolUseの基本的なHookを作ってみましょう。
Hookスクリプトを作成する
プロジェクト直下に.claude/hooks/ディレクトリを作り、スクリプトを置きます。
mkdir -p .claude/hooks#!/bin/bash
# .claude/hooks/block-dangerous-commands.sh
#
# Bashツールで危険なコマンドが実行されようとした場合にブロックするHook。
# stdin経由でPreToolUseイベントのJSONペイロードを受け取る。
# jqでtool_input.commandを取り出す
command=$(jq -r '.tool_input.command // empty')
# 危険なパターンにマッチするか確認する
if [[ "$command" =~ rm[[:space:]]+-rf[[:space:]]+/ ]] || [[ "$command" =~ ^rm[[:space:]]+-rf[[:space:]]*$ ]]; then
# permissionDecision: deny でツール実行をブロックする
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "破壊的なrm -rfコマンドはHookによりブロックされました"
}
}'
exit 0
fi
# 問題なければ何も出力せず終了(許可)
exit 0chmod +x .claude/hooks/block-dangerous-commands.shsettings.jsonにHookを登録する
.claude/settings.jsonに以下を追加します。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-dangerous-commands.sh",
"timeout": 10
}
]
}
]
}
}${CLAUDE_PROJECT_DIR}を使うことで、Claude Codeをどのディレクトリから起動してもプロジェクトルートからの相対パスでスクリプトを解決できます。
動作を確認する
新しいセッションを開始し、危険なコマンドを実行させてみます。
カレントディレクトリでrm -rf /を実行してくださいツール実行が拒否され、理由として設定したpermissionDecisionReasonの内容が表示されることを確認します。
正常なコマンドが通ることも確認する
lsコマンドでカレントディレクトリのファイル一覧を見せてくださいこちらは正常にツールが実行されることを確認し、Hookが過剰にブロックしていないかをチェックします。
Hookによる危険コマンドのブロックは有効な対策ですが、正規表現のパターンを完璧に網羅することは困難です。CLAUDE.mdの禁止事項(rm -rf・git push --force等の破壊的コマンドの非実行)を遵守する運用と組み合わせ、Hookは「最後の安全網」として位置づけましょう。
Hookの入出力の仕組み
入力: stdin経由のJSON
コマンドHookは、stdin経由でそのイベントに関するJSONを受け取ります。PreToolUseの場合、最低限以下のような情報が含まれます。
{
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf /tmp/cache"
},
"cwd": "/path/to/project",
"session_id": "abc123"
}jqコマンドで必要なフィールドだけを取り出すのが定石です。
出力: 終了コードとJSON
Hookの結果は、終了コードと標準出力のJSONの組み合わせで制御します。
| 終了コード | 意味 |
|---|---|
0 | 成功。stdout のJSONで追加の制御を行える |
2 | ブロッキングエラー。ツール実行を止める(stderrに理由を出力) |
| その他 | 非ブロッキングエラー。stderrは表示されるが処理は続行される |
PreToolUseでは、exit 2で止める方法と、JSON出力でpermissionDecision: "deny"を返す方法の2通りがあります。後者の方が「なぜ拒否したか」をUI上にきれいに表示できるため、本レッスンのサンプルでは後者を採用しています。
Hookのデバッグ方法
`/hooks`メニューで設定を確認する
Claude Codeセッション内で次を実行します。
/hooks現在読み込まれているすべてのHookと、それぞれの設定元([Project]・[User]等)が一覧表示されます。意図したHookが登録されているかをまず確認しましょう。
スクリプト単体で動作確認する
Hookスクリプトは独立したシェルスクリプトなので、Claude Codeを介さずにテストできます。
echo '{"tool_input":{"command":"rm -rf /"}}' | .claude/hooks/block-dangerous-commands.sh期待したJSONが出力されるかをここで確認すれば、「スクリプトの問題」か「Claude Code側の設定の問題」かを切り分けられます。
JSON出力の構文を検証する
Hookが期待どおりに動かない場合、出力したJSONの構文ミスが原因であることが多くあります。jqコマンドを通して文法エラーがないか確認しましょう。
.claude/hooks/block-dangerous-commands.sh < test-input.json | jq .jqがパースエラーを返す場合、Claude Code側でもJSONとして解釈できず、Hookの出力が無視されてしまいます。
timeoutを疑う
Hookが何も起こさず無視されているように見える場合、スクリプトがtimeoutの値(デフォルト600秒だが明示的に短く設定していることもある)を超えて時間がかかっていないか確認します。外部コマンドの呼び出しを含むHookは特に注意が必要です。
commandに加えてargsを指定すると「exec フォーム」となり、シェルを介さず直接プログラムが実行されます。パスにスペースを含む場合や、引数をシェル展開させたくない場合はこちらが安全です。逆にcommandだけを書く「シェルフォーム」は、パイプ(|)やリダイレクト(>)などシェルの機能を使いたい場合に向いています。意図せず混同すると、コマンドが正しく解釈されないトラブルにつながります。
よくある質問
Q. Hookの実行に失敗すると、Claude Codeの動作全体が止まりますか?
PreToolUseをexit 2またはpermissionDecision: denyで意図的に止めた場合は、そのツール呼び出しだけがブロックされます。Hookスクリプト自体が予期しないエラー(構文エラー等)で落ちた場合も、基本的にはそのツール呼び出しに対するエラーとして扱われ、セッション全体が落ちることはありません。ただし、意図しない動作を避けるため、Hookスクリプトには適切なエラーハンドリングを入れておきましょう。
Q. 複数のHookが同じイベントに登録されている場合、どう処理されますか?
PreToolUseでは複数のHookの判定結果のうち、denyが最優先されます(deny > defer > ask > allowの優先順位)。1つでも拒否すれば、他のHookが許可していてもブロックされます。
まとめ
- Hooksは特定のライフサイクルイベント(
PreToolUse、PostToolUse、SessionStart等)で自動実行されるコマンドの仕組みで、CLAUDE.mdの指示と違い確実に実行される settings.jsonのhooksキーに、イベント名 → matcher → 実行コマンドという3階層で定義するPreToolUseは実行前のブロックに、PostToolUseは実行後の後処理に向いている- Hookはstdin経由でJSONを受け取り、終了コードとstdoutのJSONで結果を返す
/hooksでの設定確認、スクリプト単体でのテスト、JSON構文検証の3段階でデバッグする
次のステップ
基本的なHookの仕組みが分かったところで、次のLesson 5では実務でよく使われるパターン——pre-commit的な品質チェック、応答完了時の通知、条件付きフックの組み合わせ——を扱い、実践コースで学んだワークフローにHooksを組み込んでいきます。