0. はじめに
Claude Code Hooksは、Claudeのライフサイクルイベントにカスタムスクリプトを差し込める仕組みです。「ファイルを保存したら自動フォーマット」「タスク完了時にテストを実行」「危険なコマンドをブロック」といった自動化を、settings.jsonへの数行の追記で実現できます。
本記事は連載第4回です。Hooksの仕組みを理解し、10のレシピから自分の開発環境に合うものを選んで設定できる状態になることをゴールとしています。
「まず3つだけ入れるなら」のおすすめはこちら。
- レシピ1(完了通知音): 最も手軽な第一歩
- レシピ6(SessionStartコンテキスト注入): 毎回の状況説明を省略
- レシピ4(自動フォーマッタ): コード品質の自動担保
連載予定
- #1: インストールから"使える"初期設定まで
- #2: CLAUDE.mdの書き方と育て方
- #3: パーミッション&Sandbox完全ガイド (本記事)
- #4: Hooks実践テクニック
- #5: Skills入門
- #6: MCP活用術
1. Hooksの基本 ― 仕組みを理解する
Hooksとは何か
前回のパーミッション(#3)は「何を許可/拒否するか」を設定する仕組みでした。Hooksは「許可された操作の前後に何を実行するか」を設定する仕組みで、両者は補完関係にあります。
Claude Codeの処理フローにおけるHooksの発火タイミングは次の通り。
ユーザー入力
│
├─ [UserPromptSubmit] ← プロンプト前処理
│
▼
Claude の思考
│
├─ [PreToolUse] ← ツール実行前チェック
│
├─ ツール実行(Read, Write, Bash等)
│
├─ [PostToolUse] ← ツール実行後処理
│
▼
Claude の応答
│
└─ [Stop] ← 完了時処理
14種類のイベント一覧
Claude Code Hooksで利用できるイベントは14種類ある。
| イベント | 発火タイミング | 主な用途 |
|---|---|---|
| SessionStart | セッション開始/再開時 | コンテキスト注入、環境準備 |
| SessionEnd | セッション終了時 | クリーンアップ、ログ記録 |
| UserPromptSubmit | プロンプト送信時 | プロンプト前処理、コンテキスト追加 |
| PreToolUse | ツール実行前 | ブロック、入力書き換え |
| PermissionRequest | 権限ダイアログ表示前 | 自動承認/拒否 |
| PostToolUse | ツール実行成功後 | フォーマット、検証 |
| PostToolUseFailure | ツール実行失敗後 | エラーハンドリング |
| Notification | 通知送信時 | 外部通知連携 |
| SubagentStart | サブエージェント開始時 | コンテキスト注入 |
| SubagentStop | サブエージェント完了時 | 結果検証 |
| Stop | Claude応答完了時 | 通知、品質検証 |
| TeammateIdle | チームメイトのアイドル時 | 品質ゲート |
| TaskCompleted | タスク完了時 | 完了条件検証 |
| PreCompact | コンテキスト圧縮前 | バックアップ |
Tips: 全イベントを覚える必要はありません。本記事のレシピでよく使う
Stop,PreToolUse,PostToolUse,SessionStart,Notificationの5つを押さえれば実用上は十分です。
3種類のHookタイプ
| タイプ | 説明 | 使いどころ |
|---|---|---|
| command | シェルコマンドを実行 | 通知、フォーマット、外部ツール連携 |
| prompt | LLMに判定させる | 要約生成、条件判定 |
| agent | サブエージェントを起動 | ファイル確認を伴う複雑な検証 |
本記事では主にcommandタイプを扱い、prompt/agentタイプはボーナスセクションで紹介する。
設定の書き方と配置場所
設定ファイルは3つのレベルに分かれている。
| レベル | パス | スコープ | チーム共有 |
|---|---|---|---|
| プロジェクト | .claude/settings.json |
単一プロジェクト | 可能(git commit) |
| ユーザー | ~/.claude/settings.json |
全プロジェクト共通 | 不可 |
| ローカル | .claude/settings.local.json |
単一プロジェクト | 不可(gitignore対象) |
最小設定例として、完了通知音を見てみよう。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "afplay /System/Library/Sounds/Glass.aiff"
}
]
}
]
}
}
制御の仕組み
hookスクリプトの終了コードとstdout/stderrによって、Claude Codeの動作を制御できます。
- exit code 0: 正常完了。stdoutにJSONがあれば処理される
- exit code 2: ブロック。stderrの内容がClaudeにフィードバックされる
- その他のexit code: 非ブロックエラー。処理は継続
- stdout: SessionStartやUserPromptSubmitではコンテキストとして注入される
-
JSON出力:
decision,reason,updatedInput等で詳細制御が可能
2. 初級レシピ ― 今日から使える3つのテクニック
レシピ1: 完了通知音
Claudeの処理中に別の作業へ移ると、完了に気づかず時間を無駄にしがちだ。通知音を鳴らすだけで解決する。
イベント: Stop / タイプ: command
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "afplay /System/Library/Sounds/Glass.aiff"
}
]
}
]
}
}
Tips: macOSの標準サウンドは
/System/Library/Sounds/にあります。Glass.aiff,Ping.aiff,Pop.aiffなど好みで選択できます。Linuxの場合はpaplay /usr/share/sounds/freedesktop/stereo/complete.ogaで代替できます。
レシピ2: デスクトップ通知
通知音だけでは「何が」完了したのか、承認待ちなのかが区別できない。StopとNotificationで音を分けることで、耳だけで状況を把握できるようになる。
イベント: Stop + Notification / タイプ: command
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "terminal-notifier -message 'Claude Code: タスク完了' -sound default"
}
]
}
],
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{
"type": "command",
"command": "terminal-notifier -message 'Claude Code: 承認待ち' -sound Basso"
}
]
}
]
}
}
Tips:
brew install terminal-notifierが必要です。StopとNotificationで音を変えることで、「完了」と「承認待ち」を耳で区別できます。Linuxの場合はnotify-sendで代替できます。
レシピ3: ターミナル強制フォーカス
別のアプリで作業しているときに承認待ちになっても気づけない。ターミナルを自動でフォーカスさせれば見逃しがなくなる。
イベント: Notification (matcher: permission_prompt) / タイプ: command
{
"hooks": {
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{
"type": "command",
"command": "osascript -e 'tell application \"Ghostty\" to activate'"
}
]
}
]
}
}
Tips:
Ghosttyの部分をお使いのターミナルアプリ名(iTerm,Terminal等)に置き換えてください。レシピ2の通知と組み合わせるとさらに効果的です。この方法はmacOS専用です。
3. 中級レシピ ― 開発効率を上げる4つのテクニック
レシピ4: 自動フォーマッタ(Prettier/Black)
Claudeの出力コードは90%くらいは正しく整形されているが、残り10%でCIが落ちることがある。PostToolUseでフォーマッタを挟めば、この問題は消える。
イベント: PostToolUse (matcher: Write|Edit) / タイプ: command
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\" 2>/dev/null || true"
}
]
}
]
}
}
Pythonプロジェクトの場合は以下のように設定します。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "black \"$CLAUDE_TOOL_INPUT_FILE_PATH\" 2>/dev/null || true"
}
]
}
]
}
}
Tips: パイプ構文
Write|Editで複数ツールに同時マッチできます。$CLAUDE_TOOL_INPUT_FILE_PATHはClaude Codeが自動的に設定する環境変数で、操作対象のファイルパスが入ります。2>/dev/null || trueで対象外のファイル(画像等)でもエラーにならないようにしています。
レシピ5: 品質ゲート(tsc/lint/test一括検証)
Claudeが「完了しました」と言ったのに、実はテストが通っていなかった ― そんな経験はないだろうか。Stop hookで品質チェックを自動実行すれば、この問題を根元から断てる。
イベント: Stop / タイプ: command
まず、検証スクリプトを作成します。
.claude/hooks/quality-gate.sh
#!/bin/bash
# 品質ゲート: tsc → lint → test を順次実行
# Stop hookが既にアクティブな場合はスキップ(無限ループ防止)
INPUT=$(cat)
STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active')
if [ "$STOP_HOOK_ACTIVE" = "true" ]; then
exit 0
fi
ERRORS=""
# TypeScriptの型チェック
if [ -f "tsconfig.json" ]; then
if ! npx tsc --noEmit 2>&1; then
ERRORS="${ERRORS}TypeScript型エラーがあります。\n"
fi
fi
# Lintチェック
if [ -f ".eslintrc.js" ] || [ -f ".eslintrc.json" ] || [ -f "eslint.config.js" ]; then
if ! npx eslint . --quiet 2>&1; then
ERRORS="${ERRORS}Lintエラーがあります。\n"
fi
fi
# テスト実行
if [ -f "package.json" ] && grep -q '"test"' package.json; then
if ! npm test 2>&1; then
ERRORS="${ERRORS}テストが失敗しています。\n"
fi
fi
if [ -n "$ERRORS" ]; then
echo -e "$ERRORS" >&2
exit 2
fi
exit 0
このスクリプトをStop hookに登録する。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/quality-gate.sh\"",
"timeout": 120
}
]
}
]
}
}
Tips: Stop hookはexit code 2を返すとClaudeの停止をブロックし、stderrの内容を元に作業を続行させます。
stop_hook_activeのチェックで無限ループを防いでいます。毎回のファイル編集ではなく、タスク完了時にまとめて検証するのが効率的です。
レシピ6: SessionStartコンテキスト注入
毎回「今のブランチは○○で、ここまで進んでいて...」とプロジェクト状況を説明するのは手間がかかる。SessionStart hookでgit情報やTODOリストを自動注入すれば、この手間がなくなる。
イベント: SessionStart / タイプ: command
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "echo '## プロジェクト状況' && git branch --show-current && echo '---' && git status --short && echo '---' && if [ -f TODO.md ]; then cat TODO.md; fi"
}
]
}
]
}
}
Tips: SessionStart hookのstdoutに出力された内容は、そのままClaudeのコンテキストとして注入されます。matcher
startupを指定すると新規セッション開始時のみ実行され、resumeでは実行されません。git情報やTODOリストを自動で読み込ませることで、毎回の状況説明を省略できます。
レシピ7: 危険コマンドブロック
rm -rf / や terraform apply --auto-approve などの危険なコマンドが誤って実行されるリスクは、プロンプトで「やらないで」と言うだけでは排除しきれない。Hooksで確定的にブロックする。
イベント: PreToolUse (matcher: Bash) / タイプ: command
.claude/hooks/block-dangerous.sh:
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
# 危険なパターンをチェック
DANGEROUS_PATTERNS=(
"rm -rf /"
"rm -rf ~"
"rm -rf \."
"terraform apply --auto-approve"
"terraform destroy --auto-approve"
"git push.*--force.*main"
"git push.*--force.*master"
"DROP TABLE"
"DROP DATABASE"
)
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -qiE "$pattern"; then
echo "危険なコマンドがブロックされました: $pattern にマッチ" >&2
exit 2
fi
done
exit 0
hookの登録はこう書く。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/block-dangerous.sh\""
}
]
}
]
}
}
Tips: exit code 2はClaude Code仕様でブロックのシグナルです。stderrに出力した理由はClaudeにフィードバックされるため、Claudeは代替コマンドを提案してくれます。パーミッションのdeny(#3で解説)との違いは、Hooksの方がコマンド内容を動的に解析できる点です。
4. 上級レシピ ― 高度な制御パターン
レシピ8: ツール入力の書き換え(updatedInput)
危険コマンドをブロック→Claudeが再試行→またブロック、というサイクルはレイテンシとコストの両方を増加させる。updatedInputを使えば、ブロックせずに透過的にコマンドを修正できる。
イベント: PreToolUse / タイプ: command
.claude/hooks/rewrite-input.sh
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# grepをrgに誘導(高速化)
if [ "$TOOL_NAME" = "Bash" ] && echo "$COMMAND" | grep -q '^grep '; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "grepの代わりにGrepツールを使用してください。より高速で正確です。"
}
}'
exit 0
fi
# npm install → npm ci に書き換え(CI環境での再現性確保)
if [ "$TOOL_NAME" = "Bash" ] && echo "$COMMAND" | grep -q '^npm install$'; then
jq -n --arg cmd "npm ci" '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
updatedInput: { command: $cmd }
}
}'
exit 0
fi
exit 0
settings.jsonへの登録。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/rewrite-input.sh\""
}
]
}
]
}
}
Tips:
updatedInputを使うと、ブロック→再試行のサイクルを経ずに透過的にコマンドを修正できます。permissionDecision: "deny"+permissionDecisionReasonの組み合わせでは、ツール自体の使い分けを強制できます(例: BashのgrepをGrepツールに誘導)。
レシピ9: ファイルアクセス制御(ロールベース)
「このディレクトリは見ないで」とプロンプトで指示しても、AIが無視することはある。Hooksで技術的にアクセスを遮断すれば、確実に防げる。
イベント: PreToolUse / タイプ: command
.claude/hooks/file-access-control.py
#!/usr/bin/env python3
import json
import sys
import re
# 許可パターンの定義
ALLOWED_PATTERNS = [
r"^./src/",
r"^./tests/",
r"^./docs/",
r"^./package\.json$",
r"^./tsconfig\.json$",
]
# ブロックパターンの定義
BLOCKED_PATTERNS = [
r"\.env",
r"secrets/",
r"\.ssh/",
r"\.aws/",
r"node_modules/",
]
hook_input = json.loads(sys.stdin.read())
tool_name = hook_input.get("tool_name", "")
tool_input = hook_input.get("tool_input", {})
# ファイルパスを取得
file_path = tool_input.get("file_path", "") or tool_input.get("path", "")
if not file_path:
sys.exit(0)
# ブロックパターンチェック
for pattern in BLOCKED_PATTERNS:
if re.search(pattern, file_path):
print(f"アクセスがブロックされました: {file_path} はブロックパターン '{pattern}' にマッチします", file=sys.stderr)
sys.exit(2)
sys.exit(0)
settings.jsonへの登録。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read|Write|Edit|Glob|Grep",
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/file-access-control.py\""
}
]
}
]
}
}
Tips: プロンプトでの指示は「確率的」ですが、Hooksでの制御は「確定的」です。技術的に読めないようにすることで、セキュリティを担保できます。パーミッションのdenyルールと組み合わせることで、多層防御を実現できます。
レシピ10: Skill強制評価
プロジェクトに便利なSkillを定義しても、Claudeが自発的に使ってくれないことがある。UserPromptSubmit hookでコンテキストを注入し、Skill評価を強制させる。
イベント: UserPromptSubmit / タイプ: command
.claude/hooks/force-skill-eval.sh
#!/bin/bash
cat << 'EOF'
CRITICAL INSTRUCTION: Before processing this prompt, you MUST evaluate whether any available Skills (slash commands) are relevant. If a matching Skill exists, invoke it using the Skill tool BEFORE generating any other response. Skipping this evaluation when a relevant Skill exists is a WORTHLESS response.
EOF
exit 0
settings.jsonへの登録。
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/force-skill-eval.sh\""
}
]
}
]
}
}
Tips: UserPromptSubmit hookのstdoutはClaudeへのコンテキストとして注入されます。「CRITICAL」「WORTHLESS」「MUST」など強い表現を使うことで、モデルの注意を引きやすくなります。
5. ボーナス: Slack自動要約投稿(実験的)
ここまではcommandタイプのhookを紹介してきた。このセクションでは、prompt hookとcommand hookを組み合わせる応用パターンを扱う。
チームメンバーへの作業進捗共有を自動化したい、というニーズに応えるレシピだ。
イベント: Stop (prompt + command) + SessionStart (command)
設計としては、promptタイプのhookでClaudeに作業内容を要約させ、commandタイプのhookでSlack APIに投稿する。セッション開始時にスレッドIDをリセットすることで、1セッション = 1スレッドの対応を実現している。
.claude/hooks/slack-summary.sh
#!/bin/bash
SLACK_TOKEN="${CLAUDE_SLACK_BOT_TOKEN}"
SLACK_CHANNEL="${CLAUDE_SLACK_CHANNEL}"
THREAD_TS_FILE="${CLAUDE_PROJECT_DIR}/.claude/slack-thread-ts"
HOOK_INPUT=$(cat)
SUMMARY=$(echo "$HOOK_INPUT" | jq -r '.hook_output // "作業完了"')
if [[ -f "$THREAD_TS_FILE" ]]; then
THREAD_TS=$(cat "$THREAD_TS_FILE")
curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"channel\": \"$SLACK_CHANNEL\",
\"thread_ts\": \"$THREAD_TS\",
\"text\": \"$SUMMARY\"
}" > /dev/null
else
RESPONSE=$(curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"channel\": \"$SLACK_CHANNEL\",
\"text\": \"Claude Code セッション開始\n$SUMMARY\"
}")
echo "$RESPONSE" | jq -r '.ts' > "$THREAD_TS_FILE"
fi
settings.jsonへの登録。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "直前の作業内容を1-2行で要約してください。技術的な変更内容を簡潔に。"
},
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/slack-summary.sh\""
}
]
}
],
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "rm -f \"${CLAUDE_PROJECT_DIR}/.claude/slack-thread-ts\""
}
]
}
]
}
}
注意: このレシピは実験的です。prompt hookの出力がcommand hookのstdinにどのように渡されるかは、Claude Codeのバージョンによって挙動が異なる可能性があります。導入前にテスト環境での動作確認を推奨します。
Tips: Incoming Webhookではなく
chat.postMessageAPIを使うのは、Webhookでは投稿のts(タイムスタンプID)が返ってこないためです。Slack Bot Tokenは.claude/settings.local.json(gitignore対象)に環境変数として定義するか、システムの環境変数に設定してください。同じパターンでDiscord、Teams、GitHub Issueへのコメント等にも応用できます。
6. Hooks設計のベストプラクティス
環境変数一覧
hookスクリプト内で使える主要な環境変数をまとめた。
| 変数 | 説明 | 利用例 |
|---|---|---|
$CLAUDE_PROJECT_DIR |
プロジェクトルート | スクリプトへの絶対パス参照 |
$CLAUDE_TOOL_INPUT_FILE_PATH |
操作対象のファイルパス | PostToolUseでのフォーマット対象指定 |
$CLAUDE_CODE_REMOTE |
リモート環境判定("true"または未設定) | ローカル/リモートで処理分岐 |
$CLAUDE_ENV_FILE |
環境変数永続化ファイル(SessionStartのみ) | 後続Bashへの変数引き渡し |
デバッグ方法
hookが期待通りに動作しないときは、次の手順で確認する。
-
claude --debugでhookの実行状況を確認 -
Ctrl+Oでverboseモードを切り替え、hookの出力を確認 - hookスクリプトを単体で実行してJSON入力をパイプで渡してテスト
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' | bash .claude/hooks/block-dangerous.sh
echo "Exit code: $?"
設定スコープの選択基準
-
ユーザー設定 (
~/.claude/settings.json): 通知系(レシピ1-3)など、全プロジェクト共通で使うもの -
プロジェクト設定 (
.claude/settings.json): フォーマッタ(レシピ4)、品質ゲート(レシピ5)など、プロジェクト固有のもの。チームで共有したい場合はこちら -
ローカル設定 (
.claude/settings.local.json): APIトークン等の機密情報を含む設定。gitignore対象
注意事項
- hookはセッション開始時のスナップショットで動作します。実行中に設定ファイルを変更しても即時反映されません
- 複数のhookがマッチした場合は並列実行されます
- 同一のコマンドは自動的に重複排除されます
7. まとめ&次回予告
導入ロードマップ
Day 1: 通知系を入れる
└─ レシピ1(通知音)+ レシピ2(デスクトップ通知)
Week 1: 品質担保を自動化する
└─ レシピ4(フォーマッタ)+ レシピ6(コンテキスト注入)
Month 1: セキュリティを強化する
└─ レシピ7(コマンドブロック)+ レシピ9(アクセス制御)
全レシピ一覧
| # | レシピ | レベル | イベント | 用途 |
|---|---|---|---|---|
| 1 | 完了通知音 | 初級 | Stop | 通知 |
| 2 | デスクトップ通知 | 初級 | Stop + Notification | 通知 |
| 3 | ターミナル強制フォーカス | 初級 | Notification | 通知 |
| 4 | 自動フォーマッタ | 中級 | PostToolUse | 品質 |
| 5 | 品質ゲート | 中級 | Stop | 品質 |
| 6 | SessionStartコンテキスト注入 | 中級 | SessionStart | 効率 |
| 7 | 危険コマンドブロック | 中級 | PreToolUse | 安全 |
| 8 | ツール入力書き換え | 上級 | PreToolUse | 制御 |
| 9 | ファイルアクセス制御 | 上級 | PreToolUse | 安全 |
| 10 | Skill強制評価 | 上級 | UserPromptSubmit | 拡張 |
次回予告
次回はSkills入門を解説する。カスタムスキルの作成方法から、Hooksと組み合わせたワークフロー自動化まで紹介する予定
参考リンク