Claude Code Hooks 実践ガイド 2026 — CI/CD連携からコード品質ゲートまで
Claude Code Hooksの設定方法から実践的なユースケースまで。PreToolUse/PostToolUseフック、Agent Teamsとの連携、Lintチェック自動化、セキュリティゲートの実装例を網羅。
エンジニアのゆとです。
Claude Code Hooksの入門記事はいくつか書いてきた。「PreToolUseでコマンドをブロックする」「Notificationでファイル変更を検知する」という基本は別記事に任せる。
この記事で扱うのは、その先の話だ。
CI/CDパイプラインとHooksをどう接続するか。Agent TeamsのTeammateIdleやTaskCompletedをどう使って品質ゲートを実装するか。HTTP HookやPrompt Hookのような2026年に追加された新しいフックタイプをどう活用するか。
Hooksを「たまに使う補助機能」ではなく「開発パイプラインの構成要素」として設計するときに必要な知識を全部詰め込んだ。
Hooksとは何か — Claude Codeの拡張ポイント
Hooksは「Claude Codeのセッションライフサイクルの特定ポイントで、ユーザー定義の処理を自動実行できる仕組み」だ。
ポイントは2つ。
1つ目は「AIが判断するのではなく、確実に実行される」という点。「このファイルを編集したあとはLintを実行してね」とシステムプロンプトに書いても、Claudeが忘れることがある。Hookに書けば確実に実行される。
2つ目は「exit codeでClaude Codeの挙動を制御できる」という点。フックスクリプトがexit 2を返すと、その処理をブロックできる。「このコマンドは実行させない」「このファイルへの書き込みは止める」という制御が確実にできる。
比喩でいうと、Gitのpre-commitフックに近い。ただしGitフックがgit commitという一点にしか刺せないのと違って、Claude Code Hooksはセッション開始からツール実行・Agent Teamsの協調まで、25種類以上のポイントに刺せる。
コードレビューや品質チェックを「お願いベース」から「仕組みベース」に変えたいなら、Hooksは必須の知識になる。
Hooksの設定方法(settings.json)
設定ファイルの場所
Hooksの設定はsettings.jsonに書く。設定ファイルには3種類の置き場所がある。
~/.claude/settings.json # 個人設定(全プロジェクトに適用)
.claude/settings.json # プロジェクト設定(リポジトリで共有可)
.claude/settings.local.json # ローカル設定(gitignoreされる)
チームで共有したいルール(危険なコマンドのブロック、セキュリティスキャン等)はプロジェクトの.claude/settings.jsonに書く。個人の作業スタイルに関わるもの(Slack通知の宛先、ローカルツールへのパス等)はsettings.local.jsonに書いておくと、うっかりコミットするリスクがない。
基本構造
settings.jsonのHooks定義の基本形はこうなる。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/validate-bash.sh",
"timeout": 10,
"statusMessage": "コマンド検証中..."
}
]
}
]
}
}
構造のポイントを整理する。
hooksの直下にイベント名(PreToolUse等)- 各イベントは配列で、複数の
matcherを持てる matcherはツール名のパターン(Bash、Edit|Write、mcp__.*等)hooks配列でそのマッチャーに対する処理を定義
フックスクリプトの配置ルール
スクリプトの置き場所は、パスプレースホルダーを使って明示的に指定できる。
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/validate.sh"
}
${CLAUDE_PROJECT_DIR}はプロジェクトルートに解決される。複数マシンで同じ設定を使いたいときに便利だ。他に${CLAUDE_PLUGIN_ROOT}(プラグインインストールディレクトリ)もある。
exec形式とshell形式の違い
コマンドの書き方には2パターンある。
// exec形式(argsを指定したとき)
// パス展開はされる、シェルの解釈なし
{
"type": "command",
"command": "node",
"args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/check.js", "--strict"]
}
// shell形式(argsを省略したとき)
// パイプ・&&・リダイレクトが使える
{
"type": "command",
"command": "eslint $FILE_PATH && echo 'OK'"
}
パイプラインを組みたい場合はshell形式、引数の安全な受け渡しが必要な場合はexec形式を使い分ける。
フックイベント一覧と使い分け
2026年現在、Claude Code Hooksには3種類のイベントカテゴリがある。
セッションレベルイベント(1セッションに1回)
| イベント | 発火タイミング | 代表的な使い方 |
|---|---|---|
| SessionStart | セッション開始・再開時 | 環境変数の読み込み、コンテキストの準備 |
| SessionEnd | セッション終了時 | ログの保存、後片付け |
| Setup | --init-only実行時 | プロジェクトの初期化処理 |
SessionStartは開発環境の準備に使いやすい。.envのバリデーションや、必要なツールのインストール確認を自動化できる。
ターンレベルイベント(1ターンに1回)
| イベント | 発火タイミング | 代表的な使い方 |
|---|---|---|
| UserPromptSubmit | ユーザーがプロンプトを送信したとき | 禁止ワードのチェック、コンテキスト追加 |
| Stop | Claudeの応答完了時 | 成果物の検証、通知送信 |
| StopFailure | APIエラーで終了したとき | エラーアラート |
Stopイベントは「タスクが完了したことを外部に通知する」用途で頻繁に使う。Slackへの完了通知やCI/CDのステータス更新が典型的だ。
エージェントループイベント(ツール呼び出しごと)
ここが実践で最も使うカテゴリだ。
| イベント | 発火タイミング | 代表的な使い方 |
|---|---|---|
| PreToolUse | ツール実行前 | コマンドのブロック、権限制御 |
| PostToolUse | ツール成功後 | 結果の検証、Lintの実行 |
| PostToolUseFailure | ツール失敗後 | エラーログ、リトライ判定 |
| PostToolBatch | 並列ツール完了後 | 次のモデル呼び出し前の処理 |
| PermissionRequest | 権限ダイアログ表示時 | 自動許可・自動拒否 |
Agent Teamsイベント(v2.1.32以降)
Agent Teamsを有効にしている場合、追加のイベントが使えるようになる。
| イベント | 発火タイミング | 代表的な使い方 |
|---|---|---|
| SubagentStart | サブエージェント開始時 | タスク開始ログ |
| SubagentStop | サブエージェント完了時 | 成果物の品質チェック |
| TeammateIdle | チームメイトがアイドル状態になったとき | 次のタスク割り当て |
| TaskCompleted | タスクが完了したとき | 品質ゲート、統合テスト |
TeammateIdleとTaskCompletedはAgent Teams特有で、あとで詳しく解説する。
その他のユーティリティイベント
| イベント | 用途 |
|---|---|
| Notification | 通知送信時のカスタマイズ |
| FileChanged | 監視ファイルの変更検知 |
| CwdChanged | 作業ディレクトリ変更時 |
| ConfigChange | 設定ファイル変更時のバリデーション |
| InstructionsLoaded | CLAUDE.md読み込み時 |
| PreCompact / PostCompact | コンテキスト圧縮前後 |
フックタイプ一覧
イベントに対してどんな処理を実行するかは5種類から選べる。
command シェルコマンド実行(最も汎用的)
http HTTPエンドポイントへのPOST(外部サービス連携)
mcp_tool MCPツールの呼び出し
prompt LLMを使った判定(「このコマンドは安全か?」等)
agent サブエージェントの起動
commandが基本で、9割のユースケースはこれでカバーできる。httpはCI/CDシステムへのWebhookに使う。promptは「このコードはセキュリティ上問題ないか」というAIによる判定に使える。
実践ユースケース5選
フックに渡されるデータはstdinのJSONだ。共通のフィールドはこうなる。
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/working/dir",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}
スクリプト内でjqを使って値を取り出すのが基本パターンだ。
1. ファイル保存時の自動Lintチェック
PostToolUseイベントでWriteまたはEditをマッチさせると、ファイル保存後に自動でLintを実行できる。
// .claude/settings.json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/auto-lint.sh",
"timeout": 30,
"statusMessage": "Lint実行中..."
}
]
}
]
}
}
#!/bin/bash
# .claude/hooks/auto-lint.sh
# stdinからtool_inputを取得
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
# ファイルが取得できなければスキップ
if [ -z "$FILE_PATH" ]; then
exit 0
fi
# 拡張子でLintツールを分岐
EXTENSION="${FILE_PATH##*.}"
case "$EXTENSION" in
js|jsx|ts|tsx)
cd "$(dirname "$FILE_PATH")" 2>/dev/null || cd "$CLAUDE_PROJECT_DIR"
npx eslint "$FILE_PATH" --max-warnings 0 2>&1
LINT_EXIT=$?
;;
py)
ruff check "$FILE_PATH" 2>&1
LINT_EXIT=$?
;;
go)
golint "$FILE_PATH" 2>&1
LINT_EXIT=$?
;;
*)
# 対象外の拡張子はスキップ
exit 0
;;
esac
if [ $LINT_EXIT -ne 0 ]; then
# exit 2でClaude Codeにエラーを表示
# ツール自体は実行済みなのでブロックにはならないが、警告として表示される
echo "Lint error in $FILE_PATH" >&2
exit 2
fi
exit 0
このパターンのポイントは「PostToolUseのexit 2はツールをブロックしない(すでに実行済み)」という点だ。ブロックしたいならPreToolUseを使う。ここではLintエラーをClaude Codeの画面に警告として表示させることが目的なので、PostToolUseで十分だ。
2. git commit前のセキュリティスキャン
PreToolUseでBashをマッチさせ、git commitを含むコマンドをインターセプトする。機密情報が含まれていたらcommitをブロックする。
// .claude/settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(git commit *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/security-scan.sh",
"timeout": 20
}
]
}
]
}
}
ifフィールドでBash(git commit *)と書くと、git commitを含むBashコマンドのときだけフックが発火する。全てのBashコマンドにフックをかけるよりパフォーマンスが良い。
#!/bin/bash
# .claude/hooks/security-scan.sh
INPUT=$(cat)
# スキャン対象パターン
PATTERNS=(
"AKIA[0-9A-Z]{16}" # AWS Access Key ID
"sk-[a-zA-Z0-9]{32}" # OpenAI API Key
"ghp_[a-zA-Z0-9]{36}" # GitHub Personal Access Token
"password\s*=\s*['\"][^'\"]+['\"]" # ハードコードされたパスワード
"SECRET_KEY\s*=\s*['\"][^'\"]{8,}['\"]" # シークレットキー
)
# ステージングエリアのdiffを取得
STAGED_DIFF=$(git diff --cached 2>/dev/null)
if [ -z "$STAGED_DIFF" ]; then
exit 0
fi
FOUND_ISSUES=""
for PATTERN in "${PATTERNS[@]}"; do
MATCH=$(echo "$STAGED_DIFF" | grep -E "$PATTERN" | head -3)
if [ -n "$MATCH" ]; then
FOUND_ISSUES+="Pattern detected: $PATTERN\n$MATCH\n\n"
fi
done
if [ -n "$FOUND_ISSUES" ]; then
# JSON形式で詳細な理由を返す
jq -n \
--arg reason "$(printf '%s' "$FOUND_ISSUES")" \
'{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: ("機密情報が検出されました。commit前に確認してください:\n" + $reason),
additionalContext: ".envファイルをgitignoreに追加してあるか確認し、機密情報をenv変数化してください。"
}
}'
exit 0
fi
exit 0
permissionDecision: "deny"でgit commitをブロックし、permissionDecisionReasonで検出された問題の詳細をClaude Codeに表示させる。additionalContextはClaude側への追加情報として渡され、次の修正提案に活用される。
3. Slack通知(作業完了時)
Stopイベントで「Claudeが1つのタスクを終えた」タイミングにSlack通知を送る。長時間かかるタスクをClaude Codeに任せているときに便利だ。
// .claude/settings.local.json(個人設定。SLACK_WHOOKのURLを含むのでローカルのみ)
{
"hooks": {
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/notify-slack.sh",
"async": true,
"timeout": 10
}
]
}
]
}
}
async: trueを指定すると、Slack通知がバックグラウンドで実行される。通知の完了を待たずにClaude Codeが次の作業に移れるので、体験がよくなる。
#!/bin/bash
# .claude/hooks/notify-slack.sh
# 環境変数 SLACK_WEBHOOK_URL が設定されている前提
if [ -z "$SLACK_WEBHOOK_URL" ]; then
exit 0
fi
INPUT=$(cat)
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
CWD=$(echo "$INPUT" | jq -r '.cwd // "unknown"')
STOP_REASON=$(echo "$INPUT" | jq -r '.stop_reason // "completed"')
# transcript_pathから最後のアシスタントメッセージを取得
TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // ""')
LAST_MESSAGE=""
if [ -f "$TRANSCRIPT_PATH" ]; then
LAST_MESSAGE=$(tail -20 "$TRANSCRIPT_PATH" | \
jq -r 'select(.type == "assistant") | .message.content[-1].text // ""' 2>/dev/null | \
tail -1 | \
cut -c1-200)
fi
PROJECT_NAME=$(basename "$CWD")
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg project "$PROJECT_NAME" \
--arg reason "$STOP_REASON" \
--arg msg "$LAST_MESSAGE" \
'{
text: "Claude Code タスク完了",
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: ("*プロジェクト*: " + $project + "\n*ステータス*: " + $reason)
}
},
{
type: "section",
text: {
type: "mrkdwn",
text: ("*最終出力*:\n" + $msg)
}
}
]
}')" \
> /dev/null 2>&1
exit 0
transcript_pathからJSONL形式のトランスクリプトを読み取って最後のメッセージを取得している。実際の作業内容をSlackに流せるのが便利だ。
4. Agent Teams品質ゲート(TeammateIdle/TaskCompleted)
これが一番「他の記事に書いてない」ユースケースだと思う。
Agent Teamsを使っているとき、各チームメイトが完了した成果物を「次に引き継ぐ前に自動検証」したい場面がある。TeammateIdleとTaskCompletedイベントを使うと、チームメイト間の受け渡しポイントに品質ゲートを挟める。
// .claude/settings.json(Agent Teams有効時)
{
"hooks": {
"SubagentStop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/agent-quality-gate.sh",
"timeout": 60,
"statusMessage": "成果物を検証中..."
}
]
}
]
}
}
#!/bin/bash
# .claude/hooks/agent-quality-gate.sh
INPUT=$(cat)
AGENT_ID=$(echo "$INPUT" | jq -r '.agent_id // ""')
AGENT_TYPE=$(echo "$INPUT" | jq -r '.agent_type // ""')
CWD=$(echo "$INPUT" | jq -r '.cwd // "."')
# ログファイルに記録
LOG_FILE="${CLAUDE_PROJECT_DIR:-$CWD}/.claude/agent-quality-log.jsonl"
echo "$INPUT" >> "$LOG_FILE" 2>/dev/null
# TypeScriptプロジェクトの場合: 型チェックを自動実行
if [ -f "$CWD/tsconfig.json" ]; then
TS_ERRORS=$(cd "$CWD" && npx tsc --noEmit 2>&1)
TS_EXIT=$?
if [ $TS_EXIT -ne 0 ]; then
# Claude Codeに型エラーの存在を通知
# SubagentStopのexit 2はエラー表示(停止ブロック)
echo "TypeScript型エラーが検出されました (agent: $AGENT_TYPE):" >&2
echo "$TS_ERRORS" | head -20 >&2
exit 2
fi
fi
# テストファイルが存在する場合: 関連テストを実行
if [ -f "$CWD/package.json" ]; then
HAS_TEST=$(cat "$CWD/package.json" | jq -r '.scripts.test // ""')
if [ -n "$HAS_TEST" ] && [ "$HAS_TEST" != "null" ]; then
cd "$CWD" && npm test -- --passWithNoTests --bail 2>&1
TEST_EXIT=$?
if [ $TEST_EXIT -ne 0 ]; then
echo "テスト失敗が検出されました (agent: $AGENT_TYPE)。次のエージェントへの引き継ぎをブロックします。" >&2
exit 2
fi
fi
fi
exit 0
SubagentStopでexit 2を返すと、Claudeが「このサブエージェントの出力に問題がある」と認識して、次のステップに進む前に対処しようとする。完全な自動修正とはならないが、「型エラーを無視して次のエージェントに渡す」という状況を防げる。
HTTP Hookを使うと、外部のCI/CDシステムにwebhookを送ることもできる。
// HTTP Hookを使ったCI/CDシステムへの通知例
{
"hooks": {
"SubagentStop": [
{
"matcher": "*",
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/api/agent-completed",
"headers": {
"Authorization": "Bearer $CI_WEBHOOK_TOKEN",
"Content-Type": "application/json"
},
"allowedEnvVars": ["CI_WEBHOOK_TOKEN"],
"timeout": 15
}
]
}
]
}
}
allowedEnvVarsで許可した環境変数だけがHTTP Hookに渡せる。セキュリティ上の配慮として、全環境変数をそのまま渡す仕組みにはなっていない。
5. 危険なコマンドのブロック(rm -rf等)
これは他の記事でも触れているが、2026年版の書き方でおさらいしておく。
従来は「exit 2を返してブロック」という方式だったが、現在はJSONのpermissionDecisionフィールドを使う方式が推奨されている。JSON出力の方が「なぜブロックしたか」をClaude Codeに正確に伝えられるからだ。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-dangerous.sh",
"timeout": 5
}
]
}
]
}
}
#!/bin/bash
# .claude/hooks/block-dangerous.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
# ブロック対象パターン
DANGEROUS_PATTERNS=(
"rm -rf /"
"rm -rf \*"
"rm -rf ~"
"rm -rf \$HOME"
"> /dev/sda"
"mkfs\."
"dd if=/dev/zero"
"chmod -R 777 /"
"chown -R .* /"
":(){:|:&};:" # Fork bomb
)
for PATTERN in "${DANGEROUS_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -qE "$PATTERN"; then
jq -n \
--arg cmd "$COMMAND" \
--arg pattern "$PATTERN" \
'{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: ("危険なコマンドのため実行をブロックしました: " + $cmd),
additionalContext: ("マッチしたパターン: " + $pattern + ". このコマンドは取り消せない破壊的操作を引き起こす可能性があります。実行が必要な場合は手動で実行してください。")
}
}'
exit 0
fi
done
# 問題なし
exit 0
permissionDecision: "deny"でブロックし、additionalContextでClaude Codeに「次はどうすべきか」のヒントを渡している。Claudeはこの情報を元に「手動での確認を求める」という判断をしやすくなる。
exit 0でJSON出力、exit 2でstderrテキスト出力という使い分けも覚えておきたい。JSON出力の方が情報量が多い。
Hooksのデバッグとトラブルシューティング
まず/hooksコマンドを叩く
Claude Code内で/hooksと入力すると、現在の設定で有効なフック一覧が表示される。
Available hooks:
PreToolUse (3 hooks)
- Bash → .claude/hooks/block-dangerous.sh [Project]
- Bash → .claude/hooks/security-scan.sh [Project]
- Write|Edit → .claude/hooks/auto-lint.sh [User]
PostToolUse (1 hook)
- Write|Edit → .claude/hooks/auto-lint.sh [User]
Stop (1 hook)
- * → .claude/hooks/notify-slack.sh [Local]
[Project]/[User]/[Local]で設定ファイルのソースが分かる。「書いたはずなのに動かない」というときは、まずここで設定が読まれているか確認する。
よくあるエラーパターン
1. スクリプトが実行権限を持っていないこれが一番多い。フックスクリプトには必ず実行権限をつける。
chmod +x .claude/hooks/*.sh
2. stdinを読んでいない
フックスクリプトはstdinでJSONを受け取る。catやjqでstdinを読まないと、jq: nullになって正しく動かない。
# 悪い例
COMMAND=$(echo $COMMAND) # stdinを読んでいない
# 良い例
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
3. stdout汚染
フックスクリプトのstdoutはJSONとして解析される。デバッグ用のechoをstdoutに出力すると、JSON解析エラーになる。デバッグ出力は>&2でstderrに向ける。
# 悪い例
echo "DEBUG: command is $COMMAND" # stdoutを汚染する
# 良い例
echo "DEBUG: command is $COMMAND" >&2
4. シェルプロファイルの干渉
.bashrcや.zshrcが何かをstdoutに出力していると、フックが誤動作することがある。
# .bashrcに書いてあるような出力は問題を起こす
echo "Welcome to my dev environment!" # これがフックのstdoutに混入する
フックスクリプト内でenv -iを使って環境をクリーンにすると解決できる場合がある。
デフォルトのタイムアウトは長めに設定されているが、ネットワークを含む処理(Slack通知、HTTPリクエスト等)は必ずasync: trueか短めのtimeoutを明示する。
{
"type": "command",
"command": ".claude/hooks/notify-slack.sh",
"async": true,
"timeout": 10
}
デバッグモードでの確認
# デバッグモードでClaude Codeを起動
claude --debug
# フックの実行ログが詳細に出力される
--debugフラグをつけると、各フックの実行タイミング・exit code・stdout/stderrの内容が全部ログに流れる。「なぜブロックされているか分からない」という状況で最も役立つ。
全フックの一時無効化
{
"disableAllHooks": true
}
問題の切り分けで「Hooksが原因か、Claude Code自体の問題か」を確認したいときに使う。
FAQ
Q. PreToolUseとPostToolUseはどっちをメインで使うべき?
目的によって使い分ける。
「やらせたくない」→ PreToolUse(実行前にブロック) 「やった後に確認したい」→ PostToolUse(実行後に検証)
Lintチェックの場合、PreToolUseで「このファイルを書き込む前にLintを通す」という使い方もできるし、PostToolUseで「書き込んだ後にLintを実行してエラーを報告する」という使い方もできる。前者の方がCI寄り、後者の方がフィードバックループとして自然だ。
Q. フックはどの順番で実行される?
同じイベント・同じマッチャーに複数のフックが定義されている場合、配列の順番通りに実行される。前のフックがexit 2を返すと、以降のフックは実行されない(ブロック扱い)。
設定ファイルの優先度はsettings.local.json > プロジェクト.claude/settings.json > ~/.claude/settings.jsonの順だが、フックは全設定ファイルから収集されてマージされる。優先度は「どのフックを実行するか」ではなく「設定値の衝突を解決する」ためのものだ。
Q. HTTP Hookで使える環境変数はどう制御する?
allowedEnvVarsフィールドで明示的に許可した環境変数だけが渡せる。
{
"type": "http",
"url": "http://localhost:8080/webhook",
"headers": {
"Authorization": "Bearer $MY_SECRET_TOKEN"
},
"allowedEnvVars": ["MY_SECRET_TOKEN"]
}
MY_SECRET_TOKENをallowedEnvVarsに書いておかないと、ヘッダーにそのまま$MY_SECRET_TOKENという文字列が渡されてしまう。環境変数の展開はallowedEnvVarsで許可されたものだけに行われる。
Q. Prompt Hookはいつ使うべき?
「人間が判断するには時間がかかるけど、AIなら一瞬で判断できる」という場面に向いている。
{
"type": "prompt",
"prompt": "このコマンドはプロダクション環境に影響を与える可能性がありますか?: $ARGUMENTS",
"model": "claude-opus-4-5",
"timeout": 15
}
ただしPrompt HookはLLM呼び出しが発生するのでコストがかかる。頻繁に発火するイベント(全てのBashコマンド等)への適用は費用対効果を考える。月に数回しか走らないデプロイコマンドのチェックなら向いている。
Q. ifフィールドとmatcherの違いは?
matcherはイベントのツール名に対してマッチングする。Bashなら全てのBashツール呼び出し、Edit|Writeなら編集・書き込みツールが対象になる。
ifフィールドはツール呼び出しの内容(引数等)でさらに絞り込む。Bash(git commit *)なら「Bashでgit commitを含むコマンドのとき」というフィルタリングができる。
matcherで対象ツールを絞り、ifでコマンドの内容を絞るという2段階になっている。
Q. フックのタイムアウトはどう設定するのが良い?
経験則だが:
- ローカルのLint実行 → 15-30秒
- テスト実行 → 60-120秒
- Slackなどの通知(async: true) → 10秒
- セキュリティスキャン → 20-30秒
タイムアウトを超えると非ブロッキングエラー(exit 1相当)として扱われる。長い処理はasync: trueでバックグラウンド化するか、タイムアウトを適切に設定する。
Q. Agent Teamsでフックを使うとトークンコストは増える?
増える。SubagentStopに品質ゲートを仕掛けてexit 2を返すと、Claudeが状況を把握して次のステップを考えるための処理が走る。それ自体がトークンを消費する。
品質ゲートで失敗 → Claudeが修正 → 再チェック というサイクルが複数回まわると、想定より多くトークンが消費される。Agent Teamsはそもそも「significantly more tokens」を消費する機能なので、Maxプランを使っていても上限に注意する。
まとめ
Claude Code Hooksの実践的な使い方を5つのユースケースで解説した。
- ファイル保存後の自動Lint(PostToolUse)
- git commit前のセキュリティスキャン(PreToolUse +
ifフィルタ) - 作業完了時のSlack通知(Stop + async)
- Agent Teams品質ゲート(SubagentStop + 型チェック)
- 危険コマンドのブロック(PreToolUse + JSON出力)
Hooksが本領を発揮するのは「チーム開発」か「Agent Teamsを使った並列処理」の場面だと思っている。一人での作業でも便利だけど、「この規則を全員に守らせたい」という場面で.claude/settings.jsonに書いてリポジトリに含めると、チーム全体の品質が上がる。
全フックイベントを網羅した公式ドキュメントは以下で確認できる。





