Claude Code Hooks チュートリアル:5つの本番用Hooksをゼロから構築
Claude Codeは約95%の確率で正しいアクションを実行します。残りの5%には、mainへのforce-push、フォーマッターのスキップ、lintに失敗するコードのコミットが含まれます。HooksはClaudeのワークフロー内の17のライフサイクルポイントに決定論的なゲートを追加することで、この5%を排除します。プロンプトの書き方やモデルの挙動に関係なく、毎回例外なく実行されます。
要約: HooksはClaude Codeのライフサイクルイベントによってトリガーされるシェルコマンドです。PreToolUse hooksはアクションを検査してブロックします(終了コード2 = ブロック、終了コード0 = 許可)。PostToolUse hooksは実行後に検証とフォーマットを行います。.claude/settings.jsonでmatcher正規表現とネストされたhooks配列を使って設定します。このチュートリアルでは、5つの本番用hooksを構築します:自動フォーマッター、セキュリティゲート、テストランナー、通知アラート、コミット前の品質チェックです。
主なポイント
- 個人開発者の方: 自動フォーマッター(Hook 1)とセキュリティゲート(Hook 2)から始めましょう。この2つのhooksは、メンテナンス不要でClaude Codeの最も一般的なミスを防ぎます。
- チームリーダーの方: リポジトリの
.claude/settings.jsonにhooksをコミットしましょう。すべてのチームメンバーが同じセーフティゲートと品質チェックを自動的に利用できます。 - セキュリティエンジニアの方: 終了コード2はアクションをブロックします。終了コード1は警告をログに記録するだけです。すべてのPreToolUseセキュリティhookは
exit 2を使用する必要があります。そうでなければ強制力がありません。
Hooksとは?
Hooksは、Claude Codeセッション中の特定のライフサイクルイベントで実行されるシェルコマンドです。LLMの外部で、Claudeのアクションによってトリガーされる通常のスクリプトとして動作し、モデルによって解釈されるプロンプトではありません。
4つの主要カテゴリが最も一般的なユースケースをカバーしています(Claude Codeは合計17のイベントタイプをサポートしています):
- セッションイベント:
SessionStartとStopはセッションの開始時または終了時に発火します。セットアップ、クリーンアップ、通知に使用します。 - ツールイベント:
PreToolUseとPostToolUseはClaudeがツールを使用する前後(ファイルの書き込み、bashコマンドの実行、コード検索など)に発火します。特定のアクションを検査してブロックできるため、最も強力なhooksです。 - 通知イベント:
NotificationはClaudeが通知を生成したときに発火します。Slack、デスクトップ通知、ログシステムへのアラートルーティングに便利です。 - サブエージェントイベント:
SubagentStopはサブエージェント(Agentツール経由で生成)が完了したときに発火します。サブエージェントのアクションに対してもhooksが発火するため、セーフティゲートが再帰的に適用されます。
終了コードのセマンティクスは重要です。 終了コード0は成功(続行)を意味します。終了コード2はアクションのブロックを意味します。終了コード1はノンブロッキングなhookエラーで、アクションは続行されます。セキュリティ上重要なすべてのhookは、ゲートを実際に強制するためにexit 2を使用する必要があります。
Hook設定の基本
Hooksは設定ファイルに記述します:
- プロジェクトレベル: リポジトリルートの
.claude/settings.json(チームと共有) - ユーザーレベル:
~/.claude/settings.json(個人用のhooks、グローバルに適用)
JSONの構造:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "/path/to/your/script.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "/path/to/another-script.sh"
}
]
}
]
}
}
各エントリにはmatcher(Bash、Write、Edit、Read、Glob、Grep、Agentなどのツール名にマッチする正規表現)と、hook定義のhooks配列があります。各hookはtype(シェルコマンドの場合は"command")と実行するcommandを指定します。Write|Editというmatcherは両方のツールタイプにマッチします。
Claude Codeセッション内で/hooksスラッシュコマンドを使ってhooksをインタラクティブに管理することもできます。
hookが発火すると、Claude Codeは環境変数(ファイル操作の場合は$FILE_PATH)とstdin(ツール名、パラメータ、セッションメタデータを含むJSONオブジェクト)を通じてコンテキストを渡します。スクリプトはこれを読み取って判断を行います。
5つの実用的なHooks
以下の各hookは、Claude Codeを主要な開発ツールとして使用する中で実際に遭遇した問題を解決するものです。すべての例で正しいネストされたhookスキーマを使用しています。
1. ファイル編集時の自動フォーマット
Claudeは機能的に正しいコードを書きますが、プロジェクトのフォーマットルールに違反することがあります。Claudeに再フォーマットを依頼する代わりに、ファイル書き込みのたびにフォーマッターを自動実行しましょう。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash -c 'if [[ \"$FILE_PATH\" == *.py ]]; then black --quiet \"$FILE_PATH\" 2>/dev/null; elif [[ \"$FILE_PATH\" == *.js ]] || [[ \"$FILE_PATH\" == *.ts ]]; then npx prettier --write \"$FILE_PATH\" 2>/dev/null; fi'"
}
]
}
]
}
}
Claude Codeは$FILE_PATHに変更されたファイルを設定します。hookは拡張子をチェックして適切なフォーマッターを実行します。Pythonファイルにはblack、JavaScriptとTypeScriptファイルにはprettierが使われます。2>/dev/nullは冗長な出力を抑制し、実際のエラーのみを表示します。
大規模なプロジェクトでは、可読性のためにインラインコマンドをスタンドアロンスクリプトに移動しましょう。
2. 危険なコマンドに対するセキュリティゲート
BashツールのPreToolUse hooksは、Claudeが実行しようとしているコマンドを検査し、危険なパターンにマッチする場合はブロックします。このhookは、リファクタリングセッション中にClaudeがmainにforce-pushした後に作成しました。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c 'INPUT=$(cat); CMD=$(echo \"$INPUT\" | jq -r \".tool_input.command\"); if echo \"$CMD\" | grep -qE \"rm\\s+-rf\\s+/|git\\s+push\\s+(-f|--force)\\s+(origin\\s+)?main|git\\s+reset\\s+--hard|DROP\\s+TABLE|:(){ :|:& };:\"; then echo \"BLOCKED: Dangerous command detected: $CMD\" >&2; exit 2; fi'"
}
]
}
]
}
}
このhookが終了コード2で終了すると、Claude Codeは保留中のコマンドをキャンセルします。エラーメッセージはターミナルとClaudeのコンテキストの両方に出力されるため、モデルはアクションが失敗した理由を理解し、より安全な代替案を提案します。
ブロックされるパターン:
- rm -rf /(ルートからの再帰的削除)
- git push --force mainとgit push -f main(mainブランチへのforce-push)
- git reset --hard(コミットされていない作業の破壊)
- DROP TABLE(偶発的なデータベース破壊)
- フォーク爆弾
このリストは環境に合わせてカスタマイズしてください。本番データベースには破壊的SQLパターンが必要です。CLIベースのデプロイにはデプロイコマンドのガードが必要です。
3. 変更後のテストランナー
ClaudeがPythonファイルを編集したとき、関連するテストを自動実行します。これにより、3つ後のファイル編集で発見するのではなく、リグレッションを即座にキャッチできます。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash -c 'if [[ \"$FILE_PATH\" == *.py ]] && [[ \"$FILE_PATH\" != *test_* ]]; then TEST_FILE=\"tests/test_$(basename \"$FILE_PATH\")\"; if [[ -f \"$TEST_FILE\" ]]; then python -m pytest \"$TEST_FILE\" -x --tb=short 2>&1 | tail -20; fi; fi'"
}
]
}
]
}
}
このhookは、編集されたファイルがPythonソースファイル(テストファイル自体ではない)かどうかを確認し、test_プレフィックスの命名規則を使用して対応するテストファイルを検索し、見つかった場合に実行します。-xフラグは最初の失敗で停止し、tail -20で出力を簡潔に保ちます。
注意: このhookは、test_プレフィックス命名のフラットなtests/ディレクトリを前提としています。ネストされたテストディレクトリや異なる命名規則の場合はパス構築を調整してください。
このhookは、Claudeが複数のファイルに触れるリファクタリングセッションで特に価値があります。即座のフィードバックループにより、テストを最後にしか実行しない場合に起こる「1つ直すと3つ壊れる」連鎖を防ぎます。
4. セッション終了時の通知
長時間実行されるClaude Codeセッションは数分かかることがあります。ターミナルを見続ける代わりに、セッション終了時に通知を受け取りましょう。
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code session ended\" with title \"Claude Code\"'"
}
]
}
]
}
}
このmacOSの例ではosascriptを使用してネイティブ通知をトリガーします。Linuxの場合はosascriptの行をnotify-send "Claude Code" "Session ended"に置き換えてください。Slack通知にはwebhookを使用します:
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-type: application/json' \
-d '{"text": "Claude Code session ended"}'
& <task>(Claude Codeのバックグラウンドモード)で開始したバックグラウンドタスクにはSlackバリアントを使用しています。インタラクティブセッションにはデスクトップ通知を使用しています。
5. コミット前の品質チェック
Claudeがgit commitを実行する前に、コードがlintを通過することを検証します。これにより、フォーマットだけでは検出できない問題をキャッチします:未使用のインポート、未定義の変数、型エラーなどです。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c 'INPUT=$(cat); CMD=$(echo \"$INPUT\" | jq -r \".tool_input.command\"); if echo \"$CMD\" | grep -qE \"^git\\s+commit\"; then if ! LINT_OUTPUT=$(ruff check . --select E,F,W 2>&1); then echo \"LINT FAILED -- fix before committing:\" >&2; echo \"$LINT_OUTPUT\" >&2; exit 2; fi; fi'"
}
]
}
]
}
}
このhookはBashコマンドがgit commitで始まる場合にのみ起動します。ruff(高速なPythonリンター)をerror、pyflakes、warningルールで実行します。問題がある場合、コミットがブロックされ(exit 2)、Claudeにlint出力が表示されます。通常、Claudeは問題を修正して再試行します。
複数の品質チェックを重ねることができます:型チェック用のmypy、セキュリティスキャン用のbandit、プロジェクト独自のバリデーションスクリプトなど。Bashコマンドに対するPreToolUse hooksにより、あらゆるシェルアクションの前にプログラマブルなゲートが実現できます。
Hookデバッグのヒント
Hooksは予想以上にサイレントに失敗します。デバッグに使用している5つのテクニックを紹介します:
- まずスクリプトを単独でテストしましょう。 サンプルのJSONをスクリプトに手動でパイプします:
echo '{"tool_input":{"command":"git commit -m test"}}' | bash your-hook.sh。Claude Codeの外で失敗するなら、内部でも失敗します。 - デバッグ出力にはstderrを使いましょう。 hookがstderrに書き込んだ内容はClaudeのコンテキストに表示されます。開発中は
echo "DEBUG: matched $CMD" >&2を書き、hookが安定したら削除しましょう。 - jqの失敗に注意しましょう。 JSONパスが間違っている場合、
jqはサイレントにnullを返し、条件文がマッチしなくなります。実際のツール入力に対してjq式をテストしましょう。 - 終了コードを確認しましょう。 終了コード2はアクションをブロックします。終了コード1は警告のみです。誤って
exit 1を使用するPreToolUse hookは、動作しているように見えながら強制力がゼロです。デフォルトは寛容に(exit 0)し、特定のブロックパターンにのみexit 2を使用しましょう。 - hooksは高速に保ちましょう。 hooksは同期的に実行されます。5秒かかるhookは、マッチするすべてのツール使用に5秒を追加します。すべてのhooksを2秒以内、理想的には500ミリ秒以内に保つようにしています。
次のステップ
これらの5つのhooksは基本をカバーしています:フォーマット、セキュリティ、テスト、通知、品質ゲートです。これらのパターンに慣れたら、コンテキスト注入(セッション開始時にプロジェクト固有の指示を追加)、再帰ガード(無限サブエージェントループの防止)、ワークフローオーケストレーション(マルチステッププロセスの連鎖)のためのhooksを構築できます。
hookアーキテクチャ、全17のライフサイクルイベント、高度なパターンについては、完全ガイドのhooksセクションをご覧ください:Claude Codeガイド:Hooksの仕組み
また、95個の本番hooksそれぞれの背景となったストーリーについても記事を書いています:Claude Code Hooks:95個のHooksそれぞれが存在する理由。各hookの動機となったインシデントについて解説しています。
FAQ
HooksでClaude Codeのコマンド実行をブロックできますか?
はい。PreToolUse hooksは終了コード2で任意のツールアクションをブロックします。Claude Codeは保留中のアクションをキャンセルし、hookのstderr出力をモデルに表示します。終了コード1はノンブロッキングなhookエラーで、アクションは続行されます。この区別は重要です:すべてのセキュリティhookはexit 1ではなくexit 2を使用する必要があります。Claudeは拒否理由を確認し、より安全な代替案を提案します。
hook設定ファイルはどこに配置しますか?
hook設定はプロジェクトレベルのhooks用に.claude/settings.json(リポジトリにコミットし、チームと共有)、またはユーザーレベルのhooks用に~/.claude/settings.json(個人用、すべてのプロジェクトに適用)に配置します。両方が存在する場合、プロジェクトレベルのhooksが優先されます。作業ディレクトリの問題を避けるため、スクリプトファイルには絶対パスの使用をお勧めします。
Hooksはサブエージェントで動作しますか?
はい。サブエージェントのアクションに対してもhooksが発火します。ClaudeがAgentツール経由でサブエージェントを生成した場合、サブエージェントが使用するすべてのツールに対してPreToolUseとPostToolUse hooksが実行されます。この動作がなければ、サブエージェントがセーフティゲートを回避できてしまいます。SubagentStopイベントでは、サブエージェントのタスク完了時にクリーンアップやバリデーションを実行できます。