Claude Code Hooksの仕組みを完全解説。5種類のイベント、終了コード制御、4つの実践設定例で、AIの操作を監査・遮断・自動化する方法を紹介。
Claude Codeがファイルを読み込み、コードを書き、コマンドを実行するたびに、バックグラウンドでイベントシステムが動いています。Hooksはそのシステムへの接続インターフェースです——任意のタイミングで独自のロジックを注入し、自動でコードチェックを行い、ログを記録し、危険な操作をブロックし、あらゆるシェルコマンドをトリガーできます。
この記事では、Hooksのメカニズム、設定方法、実践的な使い方を完全に解説します。
Hooksはsettings.jsonに設定されたシェルコマンドで、特定のイベント発生時にClaude Codeが自動的に実行します。
最もわかりやすい例え:git hooksです。gitはcommit・pushなどの操作の前後にスクリプトをトリガーできます。Claude Code Hooksも同じ考え方で、違いはトリガーポイントがAIのツール呼び出しであることです。
なぜこれが重要なのか?
Claude Codeの能力が高まるほど、確定的な制御レイヤーが必要になります。Hooksが提供するもの:
- プロンプトに依存しない実行保証(Claudeは指示を無視することがあっても、hookは必ず実行される)
- 監査可能な操作記録
- 自動化された品質チェック
| タイプ | トリガー | 典型的な用途 |
|---|---|---|
PreToolUse |
ツール呼び出し前 | 危険な操作のブロック、意図の記録 |
PostToolUse |
ツール呼び出し後 | 自動lint、テスト実行 |
PreCompact |
コンテキスト圧縮前 | 現在の状態スナップショットの保存 |
Notification |
Claudeが通知を送る時 | デスクトップ通知、Slackメッセージ |
Stop |
Claudeが応答を完了した時 | ログの集計、後続フローのトリガー |
最もよく使われるのはPreToolUseとPostToolUseで、ツール呼び出しのインターセプトと後処理に使います。
Hooksは~/.claude/settings.json(グローバル)またはプロジェクトルートの.claude/settings.json(プロジェクト単位)に記述します:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npm run lint --silent"
}
]
}
]
}
}
3つのコアフィールド:
matcher:ツール名にマッチする正規表現。このhookがどのツール呼び出し後にトリガーするかを決定します。"Write|Edit"はファイルの書き込みまたは編集時にトリガーします。空または".*"で全ツールにマッチします。type:現在は"command"のみ。command:任意のシェルコマンド。matcherを書く際に必要なツール名:
| ツール名 | 対応する操作 |
|---|---|
Write |
新規ファイルの書き込み |
Edit |
ファイルの編集 |
Bash |
シェルコマンドの実行 |
Read |
ファイルの読み込み |
Glob |
ファイル検索 |
Grep |
コンテンツ検索 |
TodoWrite |
タスクリストの更新 |
実行時にstdin経由でJSONが渡されます:
{
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.rb",
"content": "..."
},
"tool_response": "..."
}
PreToolUseはtool_input(呼び出し前の引数)を受け取り、PostToolUseはtool_response(ツールの戻り値)も受け取ります。
条件分岐ロジックを持つhookの例:
#!/bin/bash
input=$(cat)
file=$(echo "$input" | jq -r '.tool_input.file_path')
# .rbファイルのみrubocopを実行
if [[ "$file" == *.rb ]]; then
rubocop "$file" --autocorrect-all --no-color
fi
Hookの終了コードがClaude Codeの後続動作を制御します:
| 終了コード | 意味 |
|---|---|
0 |
成功、実行継続 |
2 |
ブロック:現在のツール呼び出しをキャンセルし、stderrの出力をClaudeにフィードバック |
| その他の非ゼロ | エラーを記録し、実行継続 |
終了コード2が最も有用です——PreToolUseでインターセプトロジックを書き、Claudeの操作をブロックし、その理由を伝えることができます。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash -c 'file=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path 2>/dev/null); [[ \"$file\" == *.rb ]] && rubocop -A \"$file\" --no-color -q || true'"
}
]
}
]
}
}
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c 'cmd=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .command); if echo \"$cmd\" | grep -qE \"rm.*/(migrations|seeds)\"; then echo \"migrationsまたはseedsディレクトリの削除は禁止されています\" >&2; exit 2; fi'"
}
]
}
]
}
}
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|Bash",
"hooks": [
{
"type": "command",
"command": "echo \"$(date '+%Y-%m-%d %H:%M:%S') $CLAUDE_TOOL_NAME: $(echo $CLAUDE_TOOL_INPUT | jq -c .)\" >> ~/.claude/audit.log"
}
]
}
]
}
}
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claudeが完了しました\" with title \"Claude Code\"'"
}
]
}
]
}
}
長時間のタスクが完了したら自動通知。ターミナルを見続ける必要がありません。(macOSはosascript、Linuxはnotify-send)
Hooksを配置できる場所は2つ:
グローバル(~/.claude/settings.json):すべてのプロジェクトに共通するルール(ログ、通知など)。
プロジェクト単位(.claude/settings.json):プロジェクト固有のチェック(このプロジェクトはrubocop、あのプロジェクトはeslintなど)。プロジェクト単位のhooksとグローバルhooksはマージされて両方実行されます。
チームプロジェクトでは、プロジェクト単位のsettings.jsonをgitにコミットして、全員が同じhooksを実行するようにすることを推奨します。
Hookが機能しない場合のトラブルシューティング:
writeとWriteは別物echo "hook triggered" >> /tmp/hook.logでトリガーを確認してから複雑なロジックを追加Hooksのコアバリューは予測不可能なAIの行動と確定的なエンジニアリング標準をつなぐことです。Claudeはテストの実行を忘れることがあっても、PostToolUse hookは忘れません。Claudeが誤ってファイルを削除しようとしても、PreToolUseインターセプターは通しません。
1つのhookから始めましょう:ファイル書き込み後の自動lint。それが動いたら、他のhookを追加していけばいい。