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通知発生時外部サービスへの通知転送
StopClaude が応答を終了する時追加チェック、クリーンアップ
SubagentStopサブエージェントが終了する時サブタスクの完了処理
SessionStartセッション開始時環境の初期化、前提条件チェック
SessionEndセッション終了時クリーンアップ、ログ保存
UserPromptSubmitユーザーがプロンプトを送信した時入力のバリデーション、セキュリティチェック
PreCompactコンテキスト圧縮前圧縮前の情報保存

設定構造

hookの設定は以下の構造を取る。

{
  "hooks": {
    "イベント名": [
      {
        "matcher": "パターン",
        "hooks": [
          {
            "type": "command",
            "command": "実行するコマンド",
            "timeout": 10000
          }
        ]
      }
    ]
  }
}

各フィールドの意味:

フィールド必須説明
matcherYeshookを適用する対象のパターン(ツール名等)
hooksYes実行するhookの配列
hooks[].typeYes"command"(シェルコマンド)または "prompt"(LLMプロンプト)
hooks[].commandtype=command時実行するシェルコマンド
hooks[].prompttype=prompt時LLMに送信するプロンプト
hooks[].timeoutNoタイムアウト(ミリ秒)

hookのタイプ

タイプ説明
commandシェルコマンドを実行する。stdinでJSON入力を受け取り、stdoutでJSON出力を返す
promptLLMにプロンプトを送信して判断させる。コードを書かずに柔軟なバリデーションが可能

マッチャー(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トリガーされたイベント名

イベント固有フィールド

イベント追加フィールド説明
PreToolUsetool_name, tool_input実行予定のツール名と入力パラメータ
PostToolUsetool_name, tool_input, tool_resultツール名、入力、実行結果
UserPromptSubmituser_promptユーザーが送信したプロンプト
Stop / SubagentStopreason停止理由
Notificationmessage通知メッセージ

stdout出力(hookが返すデータ)

command タイプのhookは stdout にJSONを出力して動作を制御できる。

基本出力フォーマット

{
  "continue": true,
  "suppressOutput": false,
  "systemMessage": "Claude に伝えるメッセージ"
}
フィールドデフォルト説明
continuebooleantruefalse にするとClaude の処理を中断
suppressOutputbooleanfalsetrue にするとhookの出力をClaude に表示しない
systemMessagestringなし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: ツール入力の自動修正

PreToolUseupdatedInput を使って、ツールへの入力を書き換える。

#!/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の処理は軽量にする毎回のツール呼び出しで実行されるため、重い処理はバックグラウンドに回す

参考リンク