Claude Code Hooks入門 - イベント駆動の自動化
Claude Code Hooksは、ツール呼び出しやセッション開始などのイベントに応じてシェルコマンドを自動実行する仕組み。ファイル保存時のフォーマッタ実行、コミット前チェック、通知連携などを宣言的に設定できる。
Hooksの概要
Hooksはclaude Codeのライフサイクル上の特定のタイミングに介入し、任意のシェルコマンドを実行する機能。以下のような用途に活用できる。
- ツール実行前のバリデーション(危険なコマンドのブロック等)
- ツール実行後の後処理(フォーマッタ実行、lint等)
- セッション開始・終了時の初期化・クリーンアップ
- 通知連携(Slack、LINE等への送信)
- ユーザープロンプト送信時のガードレール
設定ファイルの場所
Hooksは settings.json または settings.local.json 内の hooks キーに定義する。
| ファイル | パス | 用途 |
|---|---|---|
| ユーザー設定 | ~/.claude/settings.json | 全プロジェクト共通のhooks |
| ユーザーローカル設定 | ~/.claude/settings.local.json | 個人用(Git管理外) |
| プロジェクト設定 | .claude/settings.json | プロジェクト固有のhooks(チーム共有) |
| プロジェクトローカル設定 | .claude/settings.local.json | プロジェクト固有・個人用(Git管理外) |
settings.local.json はGit管理外の個人設定用。チームで共有する場合は settings.json を使用する。
イベントの種類
以下のイベントでhookをトリガーできる。
| イベント | タイミング | 主な用途 |
|---|---|---|
PreToolUse | ツール実行前 | バリデーション、権限制御、入力の書き換え |
PostToolUse | ツール実行後 | 後処理(フォーマット、lint)、結果の検証 |
Notification | 通知発生時 | 外部サービスへの通知転送 |
Stop | Claude が応答を終了する時 | 追加チェック、クリーンアップ |
SubagentStop | サブエージェントが終了する時 | サブタスクの完了処理 |
SessionStart | セッション開始時 | 環境の初期化、前提条件チェック |
SessionEnd | セッション終了時 | クリーンアップ、ログ保存 |
UserPromptSubmit | ユーザーがプロンプトを送信した時 | 入力のバリデーション、セキュリティチェック |
PreCompact | コンテキスト圧縮前 | 圧縮前の情報保存 |
設定構造
hookの設定は以下の構造を取る。
{
"hooks": {
"イベント名": [
{
"matcher": "パターン",
"hooks": [
{
"type": "command",
"command": "実行するコマンド",
"timeout": 10000
}
]
}
]
}
}
各フィールドの意味:
| フィールド | 必須 | 説明 |
|---|---|---|
matcher | Yes | hookを適用する対象のパターン(ツール名等) |
hooks | Yes | 実行するhookの配列 |
hooks[].type | Yes | "command"(シェルコマンド)または "prompt"(LLMプロンプト) |
hooks[].command | type=command時 | 実行するシェルコマンド |
hooks[].prompt | type=prompt時 | LLMに送信するプロンプト |
hooks[].timeout | No | タイムアウト(ミリ秒) |
hookのタイプ
| タイプ | 説明 |
|---|---|
command | シェルコマンドを実行する。stdinでJSON入力を受け取り、stdoutでJSON出力を返す |
prompt | LLMにプロンプトを送信して判断させる。コードを書かずに柔軟なバリデーションが可能 |
マッチャー(matcher)
matcher フィールドでhookを適用する対象を指定する。
| パターン | 説明 | 例 |
|---|---|---|
| ツール名(完全一致) | 特定のツールにのみ適用 | "Bash", "Edit", "Write" |
| パイプ区切り | 複数ツールに適用 | "Write|Edit" |
"*" | すべてのツール/イベントに適用 | "*" |
PreToolUse / PostToolUse で使用できる主なツール名:
| ツール名 | 対象操作 |
|---|---|
Bash | シェルコマンドの実行 |
Read | ファイルの読み取り |
Write | ファイルの新規作成・上書き |
Edit | ファイルの部分編集 |
Glob | ファイルパターン検索 |
Grep | ファイル内容検索 |
stdin入力(hookが受け取るデータ)
すべてのhookは stdin でJSON形式の入力を受け取る。共通フィールドとイベント固有フィールドがある。
共通フィールド
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.txt",
"cwd": "/current/working/dir",
"permission_mode": "ask",
"hook_event_name": "PreToolUse"
}
| フィールド | 説明 |
|---|---|
session_id | セッションの一意識別子 |
transcript_path | トランスクリプトファイルのパス |
cwd | 現在の作業ディレクトリ |
permission_mode | 権限モード("ask" または "allow") |
hook_event_name | トリガーされたイベント名 |
イベント固有フィールド
| イベント | 追加フィールド | 説明 |
|---|---|---|
PreToolUse | tool_name, tool_input | 実行予定のツール名と入力パラメータ |
PostToolUse | tool_name, tool_input, tool_result | ツール名、入力、実行結果 |
UserPromptSubmit | user_prompt | ユーザーが送信したプロンプト |
Stop / SubagentStop | reason | 停止理由 |
Notification | message | 通知メッセージ |
stdout出力(hookが返すデータ)
command タイプのhookは stdout にJSONを出力して動作を制御できる。
基本出力フォーマット
{
"continue": true,
"suppressOutput": false,
"systemMessage": "Claude に伝えるメッセージ"
}
| フィールド | 型 | デフォルト | 説明 |
|---|---|---|---|
continue | boolean | true | false にするとClaude の処理を中断 |
suppressOutput | boolean | false | true にするとhookの出力をClaude に表示しない |
systemMessage | string | なし | Claude に伝えるシステムメッセージ |
PreToolUse固有の出力
PreToolUse イベントでは追加の制御が可能。
{
"hookSpecificOutput": {
"permissionDecision": "allow",
"updatedInput": {
"command": "修正されたコマンド"
}
},
"systemMessage": "ツールの入力を修正しました"
}
| フィールド | 説明 |
|---|---|
permissionDecision | "allow"(許可)、"deny"(拒否)、"ask"(ユーザーに確認) |
updatedInput | ツールへの入力を書き換える(オプション) |
実用的な設定例
例1: ファイル編集後にPrettierを自動実行
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$TOOL_INPUT_FILE_PATH\" 2>/dev/null; echo '{\"continue\": true}'"
}
]
}
]
}
}
例2: 危険なコマンドをブロック
rm -rf / のような危険なコマンドの実行を防止する。
#!/bin/bash
# validate-bash.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$COMMAND" | grep -qE 'rm\s+-rf\s+/|mkfs\.|dd\s+if='; then
echo '{"hookSpecificOutput":{"permissionDecision":"deny"},"systemMessage":"危険なコマンドがブロックされました"}'
else
echo '{"hookSpecificOutput":{"permissionDecision":"allow"}}'
fi
対応する設定:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash /path/to/validate-bash.sh"
}
]
}
]
}
}
例3: 通知連携(LINE / Slack等)
Notification イベントで外部サービスに通知を転送する。
#!/bin/bash
# notify.sh
INPUT=$(cat)
MESSAGE=$(echo "$INPUT" | jq -r '.message // "通知"')
# LINE Notifyに送信
curl -s -X POST https://notify-api.line.me/api/notify \
-H "Authorization: Bearer $LINE_NOTIFY_TOKEN" \
-d "message=$MESSAGE" > /dev/null
echo '{"continue": true}'
{
"hooks": {
"Notification": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "bash /path/to/notify.sh"
}
]
}
]
}
}
例4: CRLF→LF自動変換
Write/Editでファイルを書き込んだ際に改行コードを自動変換する。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash /path/to/fix-crlf.sh",
"timeout": 10000
}
]
}
]
}
}
例5: promptタイプによるセキュリティチェック
コードを書かずにLLMにバリデーションを委任する例。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "prompt",
"prompt": "ファイル書き込み内容を検証してください。APIキー、パスワード、認証トークンなどの機密情報が含まれていないか確認し、含まれている場合は 'deny' を返してください。"
}
]
}
]
}
}
例6: セッション開始時の環境チェック
{
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node -v > /dev/null 2>&1 && echo '{\"continue\":true}' || echo '{\"continue\":false,\"systemMessage\":\"Node.jsがインストールされていません\"}'"
}
]
}
]
}
}
例7: ツール入力の自動修正
PreToolUse の updatedInput を使って、ツールへの入力を書き換える。
#!/bin/bash
# fix-bash-input.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# sudo が含まれていたら削除して警告
if echo "$COMMAND" | grep -q 'sudo '; then
FIXED=$(echo "$COMMAND" | sed 's/sudo //g')
jq -n --arg cmd "$FIXED" '{
"hookSpecificOutput": {
"permissionDecision": "allow",
"updatedInput": {"command": $cmd}
},
"systemMessage": "sudoを削除しました。Claude Codeからsudoは実行できません。"
}'
else
echo '{"hookSpecificOutput":{"permissionDecision":"allow"}}'
fi
複数hookの実行順序
同一イベントに複数のhookが定義されている場合、配列の順序で実行される。いずれかのhookが "continue": false を返すと、後続のhookは実行されない。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "bash check-1.sh" },
{ "type": "command", "command": "bash check-2.sh" }
]
}
]
}
}
ユーザー設定とプロジェクト設定の両方にhookが定義されている場合、両方がマージされて並列に実行される。
デバッグ方法
hookが期待通り動作しない場合の調査手順。
1. hookスクリプトを単体テスト
stdinにJSONを渡してスクリプト単体で動作確認する。
echo '{"tool_name":"Bash","tool_input":{"command":"ls"},"cwd":"/tmp","hook_event_name":"PreToolUse"}' | bash validate-bash.sh
2. ログ出力を追加
hookスクリプト内でファイルにログを書き出す。stdout はClaude Codeが読み取るため、デバッグ出力は stderr またはファイルに書く。
#!/bin/bash
INPUT=$(cat)
echo "[$(date)] hook triggered: $INPUT" >> /tmp/hook-debug.log
# ... hookの処理 ...
# stdoutにはJSON出力のみ
echo '{"continue": true}'
重要: stdout にJSON以外の文字列を出力するとパースエラーになる。デバッグ出力は必ず stderr(>&2)またはファイルに書くこと。
3. jqの存在確認
hookスクリプトで jq を使用する場合、環境に jq がインストールされていることを確認する。
# jqがない環境ではgrepで代替
if command -v jq > /dev/null 2>&1; then
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
else
COMMAND=$(echo "$INPUT" | grep -o '"command":"[^"]*"' | cut -d'"' -f4)
fi
4. タイムアウトの確認
hookの実行が遅い場合、timeout フィールドで制限時間を設定する(デフォルトは数秒程度)。タイムアウトすると hook は無視されて処理が続行される。
設定のベストプラクティス
| プラクティス | 理由 |
|---|---|
| hookスクリプトは外部ファイルに分離する | 設定ファイルの可読性向上、スクリプト単体でのテストが容易 |
stdout にはJSON出力のみ | パースエラー防止。デバッグ出力は stderr またはファイルへ |
個人設定は settings.local.json に書く | チームの設定を汚さない |
timeout を設定する | hookの無限ループやハングを防止 |
| マッチャーは必要最小限にする | "*" より具体的なツール名を指定した方が不要な実行を避けられる |
| hookの処理は軽量にする | 毎回のツール呼び出しで実行されるため、重い処理はバックグラウンドに回す |