Claude Code を使い込むと、「毎回これは止めてほしい」「編集後に必ずフォーマットしてほしい」「長いタスクが終わったら通知してほしい」といった、エージェントの動きに割り込みたい場面が出てきます。これを CLAUDE.md のお願いベースではなく、確実に実行されるフックとして組めるのが hooks です。本記事では実運用で効く 3 パターンを、コピペできる形で解説します。
※ hooks の挙動は更新が早い領域です。本記事は 2026-06 時点 の仕様に基づきます。フィールド名や挙動は公式ドキュメントで都度確認してください。
hooks とは何か
hooks は、Claude Code のライフサイクル上の決まった地点で、あなたのコマンドを実行する仕組みです。CLAUDE.md の指示が「Claude にお願いする(従わないこともある)」のに対し、hooks は ハーネスが必ず実行する点が決定的に違います。「絶対に守らせたいルール」は hooks に置くのが正解です。
よく使うイベントは次の通り。
| イベント | 発火タイミング | matcher | ブロック可否 |
|---|---|---|---|
PreToolUse | ツール呼び出しの直前 | ツール名 | 可(実行を止められる) |
PostToolUse | ツール成功の直後 | ツール名 | 不可(観測・後処理のみ) |
UserPromptSubmit | プロンプト送信時 | なし | 可 |
Stop | Claude の応答が終わったとき | なし | 可 |
SessionStart | セッション開始 / 再開 | startup 他 | 不可 |
PreToolUse と PostToolUse が最もよく使われます。前者は 実行前に検査して止められる、後者は 起きた後に反応する——この非対称性を押さえておくと設計を間違えません。
設定の置き場所と構造
hooks はプロジェクト共有なら .claude/settings.json、自分専用なら ~/.claude/settings.json に書きます。両方ある場合はプロジェクト側が優先されます。構造はこうです。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-bash.sh",
"timeout": 10
}
]
}
]
}
}
matcher はツール名("Bash"、"Edit|Write" の OR、"*" の全マッチ)。command の中の ${CLAUDE_PROJECT_DIR} はプロジェクトルートに展開されるので、パスをハードコードせずに済みます。
スクリプトには stdin から JSON が渡ってきます。中身はこんな形です。
{
"session_id": "abc123",
"cwd": "/path/to/project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "npm test" }
}
パターン1: PreToolUse で危険なコマンドを止める
まず効果が分かりやすいのが、破壊的な Bash コマンドのブロックです。.claude/settings.json に上の PreToolUse 設定を入れ、スクリプトを置きます。
#!/bin/bash
# .claude/hooks/guard-bash.sh
COMMAND=$(jq -r '.tool_input.command' < /dev/stdin)
if echo "$COMMAND" | grep -qE 'rm -rf|git push --force|: *\(\)'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "破壊的コマンドをブロックしました(guard-bash.sh)"
}
}'
else
exit 0 # 判断しない → 通常の権限フローに任せる
fi
permissionDecision は PreToolUse 専用のフィールドで、"allow" / "deny" / "ask" / "defer" を返せます。"deny" を返すとそのツール呼び出しは実行されず、理由が Claude にフィードバックされます。何も判断しないときは exit 0 で抜けるのがポイント——ここで "allow" を返すと通常の権限確認まで飛ばしてしまうので、止めたいときだけ "deny" を出すのが安全です。
パターン2: PostToolUse で編集後に自動フォーマット
ファイル編集のたびに手で整形するのは面倒です。PostToolUse で Edit / Write を捕まえ、フォーマッタを走らせます。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format.sh" }
]
}
]
}
}
#!/bin/bash
# .claude/hooks/format.sh
FILE=$(jq -r '.tool_input.file_path // empty' < /dev/stdin)
[ -z "$FILE" ] && exit 0
case "$FILE" in
*.ts|*.tsx|*.js|*.jsx|*.json|*.css|*.astro)
npx prettier --write "$FILE" >/dev/null 2>&1 ;;
esac
exit 0
PostToolUse は すでに起きたことを取り消せないため、ここでブロックを試みても意味がありません。やるのは「整える・記録する・知らせる」だけ。フォーマット結果はそのまま次のツール呼び出しに反映されるので、Claude が見るファイルも整形済みになります。
パターン3: Stop で完了を通知する
長いタスクを投げて別作業をしていると、いつ終わったか分かりません。Stop(応答完了)で macOS の通知を出します。
{
"hooks": {
"Stop": [
{ "hooks": [ { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/notify.sh" } ] }
]
}
}
#!/bin/bash
# .claude/hooks/notify.sh
osascript -e 'display notification "タスクが完了しました" with title "Claude Code"' 2>/dev/null
exit 0
Stop には matcher がありません(ツールに紐づかないため)。通知だけなら exit 0 で十分です。Linux なら notify-send、リモートなら Slack の Incoming Webhook に curl する形に差し替えられます。
exit code の意味を取り違えない
hooks のハマりどころは exit code の解釈です。ここだけは正確に。
- exit 0 — 成功。stdout の JSON が処理される(
permissionDecision等が効く) - exit 2 — ブロッキングエラー。stdout は無視され、stderr が Claude にエラーとして渡る。手早く止めたいだけなら、JSON を組み立てず
echo "理由" >&2; exit 2でもPreToolUseはツールを止められる - それ以外 — 非ブロッキングエラー。警告だけ出して処理は続行
つまり「JSON で丁寧に deny を返す」方法と「stderr に出して exit 2」の 2 通りがあり、後者は手軽な代わりに細かい制御ができません。用途で選びます。
落とし穴
PostToolUseでブロックしようとする。後処理イベントなので止められません。検査して止めたいならPreToolUseに置く- 何も判断しないのに
"allow"を返す。PreToolUseで常時allowを返すと通常の権限確認を毎回スキップしてしまう。止めたいときだけdeny、それ以外は exit 0 jq前提なのに環境に無い。hooks スクリプトは最小依存で書く。jqが無い環境向けには素のgrep/sedでフォールバックを用意する- timeout を設定せず重い処理を回す。フォーマットやテストを同期実行すると体感が重くなる。重い検査は
timeoutを短く切るか、非同期に逃がす settings.jsonの JSON が壊れている。1 文字のカンマ抜けで hooks 全体が読み込まれない。編集後はcat .claude/settings.json | jq .で構文確認する癖をつける
まとめ
hooks は「CLAUDE.md のお願い」を「ハーネスの強制」に格上げする仕組みです。まずは PreToolUse で危険コマンドを止める / PostToolUse で保存時フォーマット / Stop で通知 の 3 つから始めれば、それだけで日々の安心感と手数がはっきり変わります。チームで共有するルールは .claude/settings.json に、個人の好みは ~/.claude/settings.json に——この置き分けも最初に決めておくと後で迷いません。
自動承認・自動拒否の境界を自然言語で引く設計は、Claude Code の auto mode hard_deny 実用設計 も併せてどうぞ。