Claude Code Hooks実践ガイド — 5種ハンドラー×24イベントで実現する確定的ワークフロー自動化
Hooksとは何か——LLMを経由しない唯一の自動化レイヤー
Claude Codeの自動化機能は増え続けている。Skills、CLAUDE.md、カスタムサブエージェント……しかし、これらはすべてLLMの判断を経由する。「git pushの前に必ずテストを走らせたい」「機密ファイルの編集は絶対にブロックしたい」——そんな「確実に実行されなければ困る」処理には、唯一の確定的自動化手段であるHooksが最適解だ。
本記事では、2026年4月時点のライフサイクルイベントと4種のハンドラータイプを体系的に整理し、コピペで使える実用パターンを紹介する。
確定的実行 vs 確率的実行:設計判断フロー
Claude Codeの自動化手段は、「実行の確実性」で明確に分類できる。
- Hooks — イベント発火時に無条件で実行される。LLMの判断を一切介さない
- Skills — LLMが「使うかどうか」を判断する。プロンプトに合致すれば呼ばれるが、呼ばれない可能性もある
- CLAUDE.md — LLMが「従うかどうか」を判断する。指示として読み込まれるが、遵守は確率的
- サブエージェント — LLMが起動タイミングを判断する。複雑なワークフロー向き
設計判断のフローはシンプルだ。確実に実行すべき処理か? Yesなら迷わずHooks。LLMに判断を委ねたいならSkillsかCLAUDE.md。複雑な対話的ワークフローならサブエージェントを選ぶ。
Gitフックとの類似点と決定的な違い
Gitに馴染みがあれば、Hooksのメンタルモデルは直感的に理解できる。pre-commitはPreToolUseに、post-commitはPostToolUseに対応する。ただし決定的な違いがある——Hooksはツール単位の粒度で制御できる。「Bashツールの実行前だけ」「Writeツールで.envファイルに書き込む時だけ」といった細かな条件指定が可能だ。
4種のハンドラータイプ:適材適所の選び方
Command:シェルコマンド実行(基本形)
typeを省略した場合のデフォルト。最も使用頻度が高い。終了コードの意味は覚えておく必要がある——0で続行、2でブロック、それ以外は非ブロックエラー(処理は続行される)。
{
"hooks": {
"PreToolUse": [
{
"matcher": { "tool_name": "Bash" },
"hook": {
"type": "command",
"command": "bash -c 'if echo \"$TOOL_INPUT\" | grep -q \"rm -rf /\"; then echo \"危険なコマンドをブロックしました\" >&2; exit 2; fi'"
}
}
]
}
}
HTTP:外部エンドポイントへのPOST
urlフィールドにエンドポイントを指定する。イベントペイロードがJSON POSTされ、レスポンスボディがClaude Codeへのフィードバックになる。Slack通知やCI連携に最適だ。
{
"hooks": {
"Stop": [
{
"hook": {
"type": "http",
"url": "https://hooks.slack.com/services/T00/B00/xxxxx",
"headers": { "Content-Type": "application/json" }
}
}
]
}
}
Prompt:小型LLMに単発判定を委託
modelとpromptを指定し、軽量なLLMに判定を委ねる。Commandの終了コード制御が不要な簡易判定に向く。ただし、LLMを経由する以上確定的実行ではなくなる点に注意が必要だ。正直、これをHooksと呼ぶべきかは議論の余地がある。
Agent:サブエージェントを起動(実験的)
複雑なワークフローを自動実行するための実験的ハンドラー。安定性は要検証であり、本番環境での採用は慎重に判断したい。
実用パターン集:イベント別コピペ設定例
PreToolUse:危険操作のブロックとバリデーション
matcherフィールドで対象ツールを絞り込み、条件に合致した場合にexit 2でブロックする。stderrに出力した内容がClaude Codeにフィードバックされるため、ブロック理由を伝えられる。
{
"hooks": {
"PreToolUse": [
{
"matcher": { "tool_name": "Bash" },
"hook": {
"type": "command",
"command": "bash -c 'CMD=$(echo \"$TOOL_INPUT\" | jq -r .command // empty); if echo \"$CMD\" | grep -qE \"(rm -rf|git push --force|drop table)\"; then echo \"この操作はHooksによりブロックされました: $CMD\" >&2; exit 2; fi'"
}
},
{
"matcher": { "tool_name": "Write" },
"hook": {
"type": "command",
"command": "bash -c 'FILE=$(echo \"$TOOL_INPUT\" | jq -r .file_path // empty); if echo \"$FILE\" | grep -qE \"\\.env$\"; then echo \".envファイルへの書き込みは禁止されています\" >&2; exit 2; fi'"
}
}
]
}
}
PostToolUse:テスト自動実行とフォーマッター連携
Writeツールでファイルが書き込まれた後に、自動でlint/formatを実行するパターン。地味だが、これがあるだけでコードレビューのストレスが激減する。
{
"hooks": {
"PostToolUse": [
{
"matcher": { "tool_name": "Write" },
"hook": {
"type": "command",
"command": "bash -c 'FILE=$(echo \"$TOOL_INPUT\" | jq -r .file_path // empty); case \"$FILE\" in *.ts|*.tsx|*.js|*.jsx) npx eslint --fix \"$FILE\" 2>/dev/null;; *.py) ruff format \"$FILE\" 2>/dev/null;; esac; exit 0'"
}
}
]
}
}
SessionStart:環境セットアップの自動化
セッション開始時にNode.jsバージョンの切り替えやDocker起動を行う。毎回手動で実行していた手順をここに集約できる。
{
"hooks": {
"SessionStart": [
{
"hook": {
"type": "command",
"command": "bash -c 'source ~/.nvm/nvm.sh && nvm use 2>/dev/null; docker compose up -d 2>/dev/null; echo \"環境セットアップ完了\"'"
}
}
]
}
}
Stop:レスポンス完了時のステージングpushとレポート
Claudeがレスポンスを完了した時に、作業中の変更をstashしたり、サマリーをSlackに通知するパターン。
{
"hooks": {
"Stop": [
{
"hook": {
"type": "command",
"command": "bash -c 'cd \"$CLAUDE_PROJECT_DIR\" && if [ -n \"$(git status --porcelain)\" ]; then git stash push -m \"claude-session-$(date +%Y%m%d-%H%M%S)\"; fi'"
}
}
]
}
}
UserPromptSubmit:コンテキスト自動注入
ユーザーのプロンプト送信時に、プロジェクト固有のコンテキスト(直近のデプロイ状態など)を自動で注入できる。
設定ファイルの優先度とチーム運用
設定ファイルの優先度
Hooksの設定は以下の優先度で読み込まれる(上が最優先)。
- マネージドポリシー — 組織レベルで管理者が強制(最優先、個人設定で上書き不可)
- CLIフラグ — コマンドライン引数による指定
- ローカル —
.claude/settings.local.json(.gitignore推奨) - プロジェクト —
.claude/settings.json(リポジトリにコミット可能) - グローバル —
~/.claude/settings.json(全プロジェクト共通) - プラグイン / スキルフロントマター
チーム共有フック vs 個人フック
使い分けの基準は明確だ。
.claude/settings.jsonにコミットすべきフック:
- 危険操作(
rm -rf、force push)のブロック - lint/formatの自動実行
.envファイルへの書き込み防止
settings.local.jsonに留めるべきフック:
- 個人のSlack通知
- エディタ連携(VSCode / Cursor固有の設定)
- ローカル環境固有のDocker起動スクリプト
組織レベルではマネージドポリシーにより、セキュリティ関連のフックを全メンバーに強制できる。個人がローカル設定で上書きしても、マネージドポリシーのフックは必ず実行される。
ハマりポイントと設計上の注意点
終了コードの罠:exit 1はブロックしない
これが最大の落とし穴だ。多くの開発者が「エラー = exit 1」と条件反射で書いてしまうが、Hooksにおいてexit 1は非ブロックエラーであり、処理は続行される。
なお、exit 2によるブロックが有効なのはPreToolUseなどの一部イベントに限られる。PostToolUse、Notification、SessionStartなどのイベントではexit 2を返してもブロックは無視され、処理が続行される。ブロック機能を前提とする場合は、対象イベントがexit 2に対応しているか確認すること。
# NG: exit 1 → 処理は続行されてしまう
if echo "$CMD" | grep -q "rm -rf"; then
echo "危険な操作です" >&2
exit 1 # ブロックされない!
fi
# OK: exit 2 → PreToolUse等の対応イベントで処理がブロックされる
if echo "$CMD" | grep -q "rm -rf"; then
echo "危険な操作です" >&2
exit 2 # 正しくブロックされる
fi
stderrの扱いは終了コードによって異なる。exit 2(ブロック)の場合はstderrの内容がそのままエラーメッセージとしてClaude Codeに渡される。exit 1などの非ブロックエラーの場合は、stderrの1行目のみがトランスクリプトに表示される。詳細なエラー情報を伝えたい場合は1行目に要約を書くこと。
タイムアウトとパフォーマンス影響
Commandハンドラーにはデフォルトのタイムアウトがある。テストスイートの実行のような重い処理をPreToolUseに入れると、タイムアウトで意図しない挙動になることがある。重い処理はHTTPハンドラーに逃がすか、バックグラウンド実行を検討しよう。
Hooksを使うべきでないケース
万能に見えるHooksだが、以下のケースでは別の手段が適している。
- LLMの出力内容に応じた条件分岐 → Skills
- 複雑な対話的ワークフロー → サブエージェント
- ドキュメント的な指示・コーディング規約 → CLAUDE.md
- プロジェクト固有のコマンド体系 → Skills(スラッシュコマンド化)
まとめ
Claude Code Hooksは、LLMの確率的判断に頼らず「必ず実行される」唯一の自動化手段だ。4種のハンドラータイプと各種ライフサイクルイベントを理解すれば、危険操作の確実なブロックからCI連携、チーム全体のワークフロー標準化まで、あらゆる確定的自動化が実現できる。
まずはPreToolUseでの危険操作ブロックとPostToolUseでのlint自動実行から始めてみてほしい。この2つだけでも、Claude Codeとの協働体験が明確に変わるはずだ。
