Claude CodeのHooksとは?実例付きで使い方を解説

※当サイトは、アフィリエイト広告を利用しています。

Claude CodeのHooksを使って「危険なコマンドを止めたい」「ファイル変更の監査を自動化したい」と考えていませんか。

本記事ではHooksの全体像、ライフサイクル、出力ルール(exit code / JSON)の違い、実運用で使える具体例(rmブロック、非同期テスト、設定ファイル監査)まで、手を動かして確かめられる形でまとめます。

Hooksとは?

Claude CodeのHooksとは、特定のタイミング(イベント)で任意の処理を差し込める仕組みです。
Hooksを使うことで、「危険コマンドのブロック」、「特定ツールの禁止」、「ファイル書き込み制御」などが可能になります。

知りたイヌ

Hooksって何から覚えればいいの?

くれとむ

まずはイベントとmatcher→handler→decisionの流れを押さえよう。

基本の流れ

Claude CodeのHooksは「イベント発火 → matcherでフィルタ → handlerを実行 → exit code/JSONでdecisionを返す」というシンプルな流れです。

代表的なイベントはセッション開始〜セッション終了の間に発生します。
ツール呼び出し前後(PreToolUse / PostToolUse)、設定変更(ConfigChange)、権限ダイアログ(PermissionRequest)など、用途ごとに適切なイベントを選びます。

hookフックイベントとは?

典型的なライフサイクルではSessionStart → 各種ツール使用(PreToolUse → Tool実行 → PostToolUse/PostToolUseFailure) → SessionEndという流れになります。

用途に応じて適切なhookイベントを選んでください。

代表的なイベントと推奨ユースケース
  • SessionStart / SessionEnd:セッション全体の初期化やクリーンアップ。ログ開始・終了のトリガー。
  • PreToolUse / PostToolUse / PostToolUseFailure:ツール呼び出し前後のチェックや監査ログ記録。危険コマンドのブロックはPreToolUseで実施。
  • PermissionRequest:権限リクエストを自動承認/拒否するロジックを入れる場面。
  • ConfigChange / WorktreeCreate/Remove:設定やワークツリーの変更監査。変更の差分チェックや署名検証をここで行う。
  • Stop / SubagentStart/Stop / TaskCompleted:エージェント停止やサブタスク完了の検証・通知。

matcherとは?

どのツール/どの条件の時に、そのhookを発火させるかを指定するフィルタです。
matcherで無関係なログを弾くことで無駄な処理を減らせます。
主に、PreToolUseやPostToolUseなどツール関連イベントで使います。

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/block-rm.sh"
}
]
}
]
}
}

matcherがないとどうなる?
  • 不要なツールにもhookが走る
  • パフォーマンス悪化
  • 想定外のdenyが発生

特にPreToolUseでdenyする場合は、matcherを明示するのが安全です。

出力ルール(exit code / JSONの選び方)

出力は大きく「exit code」と「純粋なJSON」の2種類です。

知りたイヌ

exit codeとJSON、どっちを使えばいいか迷う。

exitコード

exit codeは単純なcontinue/deny/handler-failureの合図に向きます。

exitコード
  • exit 0:成功(継続)
  • exit 2:ブロッキング(当該アクションを停止)
  • その他:非ブロッキングエラー(エラー扱いだが処理は継続)
#!/bin/bash
# stdin から JSON 入力を読み取り、コマンドをチェック
command=$(jq -r '.tool_input.command' < /dev/stdin)

if [[ "$command" == rm* ]]; then
  echo "Blocked: rm commands are not allowed" >&2
  exit 2  # ブロッキング エラー: ツール呼び出しが防止される
fi

exit 0  # 成功: ツール呼び出しが進行

純粋なJSON

JSONはより細かいdecision(permissionDecisionやhookSpecificOutput)を返すときに用います。

JSON

PreToolUseではJSONでhookSpecificOutput.permissionDecisionを返すと明示的に許可/拒否できます。
JSONを返す場合は必ず「純粋なJSONのみ」をstdoutに出してください。
余計なログが混ざるとパーサが弾き、フックが失敗します。

#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "Destructive command blocked by hook"
    }
  }'
else
  exit 0  # allow the command
fi

実際に動かしてみる

ここでは「準備/設定/動作確認/注意点」の順で、すぐに使える具体例を示します。
スクリプトはコピペで動く最小実装を掲載しています。実運用では必ずレビューと隔離環境での確認を行ってください。

危険なBashコマンドをブロックする

目的:rm -rfなどの破壊的コマンドをPreToolUseで自動的に止めること。

設定例(.claude/hooks/block-rm.sh)

#!/bin/bash
# .claude/hooks/block-rm.sh
set -euo pipefail
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$COMMAND" | grep -qE 'rm -rf|rm -R'; then
  jq -n '{
    "hookSpecificOutput": {
      "hookEventName": "PreToolUse",
      "permissionDecision": "deny",
      "permissionDecisionReason": "Destructive command blocked by hook"
    }
  }'
  exit 0
fi
exit 0

上のスクリプトは標準入力のJSONからcommandフィールドを取り出して判定しています。該当する場合はJSONでpermissionDecisionを返し、exit 0で正常終了させています。

設定例(.claude/settings.json)

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}
動作確認手順の一例
  1. ファイルを作成して保存:.claude/hooks/block-rm.sh
  2. 実行権を付与:chmod +x .claude/hooks/block-rm.sh
  3. セッションを起動:claude --debug を実行
  4. Bashで rm -rf /tmp/test を呼び、フックがコマンドを拒否することを確認行

ファイル書き込み後に非同期でテストを走らせる

目的:Write/Edit操作の後、非同期でテストを実行して結果を記録・通知する。asyncフックを使えばユーザの作業をブロックしません。

設定例(.claude/hooks/run-tests-async.sh)

#!/bin/bash
# .claude/hooks/run-tests-async.sh
set -euo pipefail
# 標準入力はhookのJSON。最低限のログだけ出力する。
(
  cd "$CLAUDE_PROJECT_DIR" || exit 0
  npm test > .claude/tests-last-run.log 2>&1 || true
  if grep -q "failing" .claude/tests-last-run.log 2>/dev/null; then
    echo "tests:fail" > .claude/tests-status
  else
    echo "tests:ok" > .claude/tests-status
  fi
) &
# 非同期フックは決定を返せないため、単に exit 0
exit 0

設定側(.claude/settings.json)でasync: trueを指定します。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/run-tests-async.sh",
            "async": true
          }
        ]
      }
    ]
  }
}
動作確認手順の一例
  1. ファイルを作成して保存:.claude/hooks/run-tests-async.sh
  2. 実行権を付与:chmod +x .claude/hooks/run-tests-async.sh
  3. セッションを起動:claudeを実行
  4. プロンプト実行:Write、Editツールを使うような指示を実行

asyncなので、以下のようにセッション上はHooks実行に関するものが特に何も表示されません。

以下のようなログファイルが出力されます。
npmコマンドは実行失敗していますが、コマンドが実行されていることが確認できました。

知りたイヌ

async hooksってどう使えばいいの?

くれとむ

長時間処理はasyncを使うと良いよ。

ポイント
  • 対象:commandフックのみで async: true が指定可能。
  • 挙動:Claudeはフックの完了を待たずに処理を進めます。結果は次ターンや別チャネルで通知する設計にする。
  • 注意点:asyncフックは即時に決定を返せないため、ブロック目的には使えません(結果は次ターンでsystemMessage等に反映する設計にする)。

特定ファイル(例:.claude/settings.json)の変更を拒否する

目的:プロジェクト設定ファイルが不正に変更されるのを検出・拒否すること。

設定例(.claude/hooks/config-audit.sh):

#!/bin/bash
# .claude/hooks/config-audit.sh
set -euo pipefail

# jq が無いと JSON を読めないので安全にスキップ(ログだけ残す)
if ! command -v jq >/dev/null 2>&1; then
  exit 0
fi

INPUT="$(cat)"

# file_path が無いイベントもある
FILE_PATH="$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')"
if [[ -z "${FILE_PATH}" ]]; then
  exit 0
fi

# CLAUDE_PROJECT_DIR が無い環境もあり得るので保険
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"

# relative 化できない場合は元のパス
NORMALIZED_PATH="$(realpath --relative-to="$PROJECT_DIR" "$FILE_PATH" 2>/dev/null || echo "$FILE_PATH")"
BASENAME="$(basename "$NORMALIZED_PATH")"

# 例: settings.json をブロック(必要ならパス一致に変える)
if [[ "$BASENAME" == "settings.json" ]]; then
  jq -n '{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Unauthorized settings change" } }
  exit 0
fi

exit 0

このスクリプトは対象ファイルがsettings.jsonでかつ変更元がproject_settingsの場合にブロックする簡易例です。実運用では差分の署名検証や別途バックアップを取るフローを必ず用意してください。

設定例(.claude/settings.json):

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/config-audit.sh"
          }
        ]
      }
    ]
  }
}
動作確認
  1. ファイルを作成して保存:.claude/hooks/run-tests-async.sh
  2. 実行権を付与:chmod +x .claude/hooks/run-tests-async.sh
  3. セッションを起動:claudeを実行
  4. プロンプト実行:.claude/settings.jsonを書き換えるような指示を実行

注意点
  • 念の為、settings.jsonが書き換えられても問題ないようにバックアップをとっておきましょう

tips

以下のようなログ出力の仕組みを、hooks/*.sh内に含めておくと、Claude内部でどのような環境変数が使われていて、どの環境変数を元に判定すれば良いのかが分かるので便利です。

# ====== ログ開始 ======
LOG_DIR=".claude/logs"
mkdir -p "$LOG_DIR"
TIMESTAMP="$(date +"%Y%m%d_%H%M%S")"
LOG_FILE="$LOG_DIR/config_audit_$TIMESTAMP.log"

{
  echo "===== Config Audit Hook Triggered ====="
  echo "Timestamp: $(date)"
  echo "Input: $INPUT"
  echo "File Path: $FILE_PATH"
  echo "Normalized Path: $NORMALIZED_PATH"
  echo "Basename: $BASENAME"
  echo
} >> "$LOG_FILE"
# ====== ここまで ======
くれとむ

不要な環境変数の露出は情報漏洩の元なので、必ずデバッグ用途でのみ使い本運用する場合はログを出力しないように変更しておきましょう。

スコープ管理

Hooksは定義場所により影響範囲が変わります。誤って全ユーザーに危険なフックを配布しないようスコープ設計が重要です。

主要な配置先と作用範囲
  • ~/.claude/:ユーザ全体—そのマシンのすべてのプロジェクトに適用されます(個人向け設定に適する)。
  • .claude/settings.json:プロジェクト単位—特定プロジェクト内でのみ有効(推奨の導入場所)。

hookハンドラーフィールドの使い分け

高度なユースケースでは、agent hooks / HTTP hooksを適材適所で組み合わせると強力です。
以下は代表的な特徴と注意点です。

タイプ判定方法長所短所
commandコマンド速い・シンプル・デバッグしやすい複雑な意味理解が苦手
prompt単一プロンプトでLLM評価簡易・構造化したLLM判定が可能深い検証には向かない
agentサブエージェントで複数ターン詳細な調査・検証が得意設定が複雑・重い
http外部APIにPOST外部サービスの利用やログ連携に強いLLM判断は外部に依存

commandについて

指定したシェルコマンド/スクリプトを実行してその終了コード(exit code)やstdoutのJSONによってClaude Codeの動作を制御する仕組みです。
これが最もシンプルで決定論的なフックです。

  • rm -rfなどの不可逆コマンドの制御
  • /etcなどへの書き込み制御
  • WebSearch/WebFetchの禁止

実装例は、上記で紹介した使用例の通りです。

promptについて

入力をClaudeCodeにプロンプトとして渡し、LLM出力で判断させる仕組みです。promptフックは、Claude自身に判断させたい時に使います。

  • コマンドに危険なオプションが含まれていないかチェック
  • プロンプトが禁止ワードを含む場合にブロック
  • 厳密なガイドラインに沿って自然言語で検証

実装例(終了後に実施内容を説明してもらうpromptフックを追加)

{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Explain what you just did and why you did it."
}
]
}
]
}
}


agentフックについて

promptの強化版で、単発のLLM判定ではなく、サブエージェントを起動して複数ターン+ツール(Read/Grep/Glob等)を使って調査→判定します。
サブエージェントでリポジトリ内を読み取り、静的解析やテストを行う場面に有効です。
制約としてターン数や実行時間の上限があります。

  • 本当にテストが通っているか確認
    (テスト結果ファイルを読む、失敗箇所を探す、判断する)
  • 差分に危険コードが含まれていないか確認
    (レポジトリ横断チェック、複数ファイル整合性確認)

実装例

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

HTTPフックについて

Hook入力JSONを、HTTP POSTのbodyとして指定URLへ送信し、外部サービス側で検証・記録・通知などを行い、レスポンスbodyで結果を返せるタイプです。

  • 社内ポリシーサーバーに問い合わせ
    (危険コマンドを中央ポリシーサーバーで判定)
  • 外部サービスへの承認フロー連携
    (Slackに通知、チケット発行)

実装例

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "http",
            "url": "http://localhost:8080/hooks/pre-tool-use",
            "timeout": 30,
            "headers": {
              "Authorization": "Bearer $MY_TOKEN"
            },
            "allowedEnvVars": ["MY_TOKEN"]
          }
        ]
      }
    ]
  }
}

まとめ

まずは小さく安全なフック(例:rmブロック)から導入し、claudeで挙動を確認しながら段階的に運用へ移行してください。

  1. 小さなPreToolUse(例:.claude/hooks/block-rm.sh)を作成する。
  2. 実行権を付与し(chmod +x)、claude --debug で動作確認する。
  3. 非同期処理は async: true を用いて段階的に導入する(例:run-tests-async.sh)。
  4. スコープ(ユーザ/プロジェクト/管理ポリシー)を明確に定義する。
  5. 用途に応じたhookハンドラーフィールドで拡張していく。
知りたイヌ

小さく始めて確認を重ねるのが安心だね。

くれとむ

本番投入前にはレビュー、バックアップ、ログ設計を必ず行ってください。










コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


ABOUT US
くれとむ
IT企業で働いているシステムエンジニアです。 AWSなどIT技術のトレンドを発信します。 また、日常の課題を解決するライフハック記事や実体験をもとにしたレビューも発信します。 エンジニアならではの視点で、技術の楽しさと日常の快適さを繋げます! AWS認定(CLF, SAA, DVA, SOA, SAP, DOP, ANS, SCS, MLS)、基本情報技術者、応用情報技術者、情報処理安全確保支援士、TOEIC L&R 870点 ※このサイトはアフィリエイト広告(Amazonアソシエイト含む)を掲載しています。