AI2026-04-14📖 9分

Claude Code Hooks完全ガイド|カスタムフック作成と活用テクニック

Claude Code Hooksの仕組みと活用方法を解説。イベントフック・PreToolUse/PostToolUse・設定方法から、開発効率を上げる実践的なHookレシピまで。

髙木 晃宏

代表 / エンジニア

👨‍💼

Claude Codeとは?完全ガイドでClaude Codeの全体像を把握し、CLAUDE.mdでプロジェクトルールを定義し、Skillsで繰り返し作業をテンプレート化する。こうした仕組みを活用してAI駆動の開発環境を整えてきた方も多いのではないでしょうか。しかし、これらの仕組みには共通する限界があります。それは、すべてが「お願い」であるということです。CLAUDE.mdに書いたルールをClaudeが無視する可能性はゼロではなく、Skillsの指示も確率的に解釈されます。Hooksは、この不確実性を排除するために設計されました。シェルスクリプトをClaude Codeのワークフローの特定のタイミングで確実に実行する仕組みであり、CLAUDE.mdが「守ってほしいルール」だとすれば、Hooksは「必ず実行されるルール」です。本記事では、Hooksの仕組みから実践的なレシピまでを、私たちaduceでの運用経験を交えて解説します。

Claude Code Hooksとは

Claude Code Hooksは、Claudeのワークフローにおける特定のポイントでシェルスクリプトを自動実行する仕組みです。たとえば「ファイルを編集した直後にフォーマッタを走らせる」「特定のディレクトリへの書き込みをブロックする」「すべてのツール呼び出しをログに記録する」といった処理を、Claudeの動作に介入する形で確実に実行できます。

ここで重要なのは「確実に」という点です。CLAUDE.mdやSkillsに書いた指示は、あくまでClaude(LLM)へのプロンプトであり、解釈は確率的です。対してHooksはClaudeの推論とは独立して動作するシェルスクリプトであり、条件を満たせば100%実行されます。この違いを理解することが、Hooksを正しく活用するための第一歩です。

CLAUDE.mdとの本質的な違い

CLAUDE.mdに「supabase db reset は絶対に実行しない」と書いたとしましょう。99%の確率でClaudeはそのルールを守るかもしれません。しかし、コンテキストが長くなったとき、複雑なタスクを処理しているとき、あるいは単にLLMの確率的な性質によって、そのルールが破られる可能性はゼロにはなりません。

Hooksであれば、この問題を根本的に解決できます。PreToolUseイベントで「Bashツールの入力に supabase db reset が含まれていたら実行を拒否する」というフックを設定すれば、Claudeの意図に関係なく、そのコマンドは物理的に実行されません。これがHooksの最大の価値です。

Hooksの動作の仕組み

Hooksはシンプルな契約で動作します。指定したイベントが発生すると、Claude Codeはフックとして登録されたシェルコマンドを実行します。フックの終了コードと標準出力を確認し、その結果に基づいて処理を続行・中断・変更します。

具体的には、終了コード0は成功を意味し、標準出力にJSON形式の制御命令を含めることで、Claudeの動作を細かく制御できます。終了コードが0以外の場合はエラーとして扱われ、標準エラー出力の内容がClaude Codeに報告されます。

この仕組みにより、Hooksはただの通知機能ではなく、Claude Codeの動作を能動的に制御するゲートキーパーとして機能します。

Hooksの種類とイベント一覧

Hooksは5つのイベントタイプに分類されます。それぞれのイベントが発火するタイミングと用途を整理します。

PreToolUse

ツールが実行される「前」に発火するイベントです。Hooksの中でも最も利用頻度が高く、強力なイベントといえます。

Claude: Bashツールを実行しようとしている -> PreToolUse Hook が発火 -> exit 0 + {"decision": "allow"} -> 実行許可 -> exit 0 + {"decision": "deny", "reason": "..."} -> 実行拒否 -> exit 0 + {"updatedInput": {...}} -> 入力を変更して実行

とくに重要な特性として、PreToolUseはパーミッションチェックの「前」に発火します。つまり、ユーザーがバイパスモード(--dangerouslySkipPermissions)で実行している場合でも、Hookでの拒否が優先されます。これは安全面で極めて有用な特性です。

たとえばCI/CDパイプラインでClaude Codeを自動実行する場合、パーミッションを全許可しつつも「本番DBへの接続コマンドだけは絶対にブロックする」といったガードレールを設けることが可能になります。

matcherとしてツール名を指定でき、"Bash""Edit""Write" のように特定のツールだけに反応するフックを設定できます。"*" を指定すればすべてのツール実行に対して発火します。

PostToolUse

ツールが実行された「後」に発火するイベントです。ツールの実行結果を受けて後処理を行うのに適しています。

Claude: Editツールでファイルを変更した -> PostToolUse Hook が発火 -> フォーマッタを実行してコードを整形 -> リンターを実行してエラーを検出 -> 変更内容をログに記録

PostToolUseもPreToolUseと同様にmatcherでツール名を指定できます。典型的な用途は、ファイル変更後の自動フォーマットやリント実行です。Claudeが生成したコードがプロジェクトのフォーマット規約に従っていない場合でも、PostToolUseフックでPrettierやESLintの --fix を走らせれば、常にフォーマット済みのコードが維持されます。

また、PostToolUseの標準出力にJSONで {"suppressOutput": true} を返すと、ツールの出力がClaudeに渡されなくなります。大量のログ出力を伴うツール実行で、不要な情報をClaudeのコンテキストに入れたくない場面で活用できます。

Notification

Claude Codeがユーザーに通知を送るタイミングで発火するイベントです。

このイベントは、たとえばClaudeが長時間の処理を終えたときや、ユーザーの入力を待っているときに発生します。デスクトップ通知のカスタマイズや、Slackへの通知転送といった用途に利用できます。matcherの概念はなく、すべての通知イベントに対して発火します。

Stop

Claudeがレスポンスの生成を完了し、処理を停止する直前に発火するイベントです。

Claudeの最終的な出力を検証したり、後始末の処理を行ったりするのに適しています。たとえば「Claudeが生成したコードがビルドを通るか確認する」「タスク完了の通知を送る」といった用途が考えられます。

Stopフックの標準出力にJSONで {"decision": "block", "reason": "..."} を返すと、Claudeの停止を阻止し、指定した理由を伝えて処理を続行させることができます。「ビルドが通るまでClaudeに修正を続けさせる」といったワークフローが実現可能です。

SubagentStop

サブエージェント(Claudeがタスクを委譲した子プロセス)が処理を完了する直前に発火するイベントです。

Stopと同様の制御が可能ですが、対象がメインのClaudeではなくサブエージェントである点が異なります。サブエージェントの出力品質を検証し、基準を満たさない場合はやり直しを指示する、といった制御が可能です。

イベント一覧のまとめ

イベントタイミングmatcher主な用途
PreToolUseツール実行前ツール名実行制御、入力の検証・変更
PostToolUseツール実行後ツール名後処理、フォーマット、ログ
Notification通知時なし通知のカスタマイズ
Stop停止前なし出力検証、後始末
SubagentStopサブエージェント停止前なしサブエージェント出力の検証

settings.jsonでのHook設定方法

Hooksは settings.json に定義します。設定ファイルの配置場所は2つあり、用途に応じて使い分けます。

設定ファイルの配置場所

プロジェクト設定(チームで共有): .claude/settings.json

プロジェクトルートの .claude/ ディレクトリに配置します。Gitにコミットすることでチーム全体で共有できます。プロジェクト固有のフォーマッタ実行や、保護対象ディレクトリの定義などに適しています。

ユーザー設定(個人用): ~/.claude/settings.json

ホームディレクトリの .claude/ に配置します。すべてのプロジェクトに横断的に適用される個人的なフックに適しています。たとえば、すべてのツール呼び出しを個人のログファイルに記録する、といった用途です。

基本的な設定構造

settings.jsonにおけるHooksの設定は以下の構造をとります。

{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "echo 'Bashツールが実行されます'" } ] } ] } }

各フィールドの意味を確認しましょう。

  • hooks: Hooksの設定全体を格納するトップレベルキー
  • "PreToolUse": イベントタイプ。"PostToolUse""Notification""Stop""SubagentStop" のいずれかを指定
  • matcher: 反応するツール名。"Bash""Edit""Write""*"(全ツール)など
  • hooks: 実行するフックの配列。複数のコマンドを順番に実行可能
  • type: 現時点では "command" のみ
  • command: 実行するシェルコマンド

環境変数

フックのシェルコマンドには、イベントに応じた環境変数が渡されます。

  • CLAUDE_TOOL_NAME: 実行されるツール名(PreToolUse/PostToolUse)
  • CLAUDE_TOOL_INPUT: ツールへの入力(JSON文字列)
  • CLAUDE_TOOL_OUTPUT: ツールの出力(PostToolUseのみ)
  • CLAUDE_NOTIFICATION: 通知メッセージ(Notificationのみ)
  • CLAUDE_STOP_RESPONSE: Claudeの最終レスポンス(Stop/SubagentStopのみ)

これらの環境変数を使うことで、ツールの入力内容を判定して条件分岐したり、出力を外部サービスに送信したりできます。

JSON出力による制御

フックの標準出力にJSONを出力することで、Claude Codeの動作を制御できます。主な制御オプションは以下のとおりです。

PreToolUseでの制御:

{"decision": "allow"}

ツールの実行を明示的に許可します。パーミッションプロンプトもスキップされます。

{"decision": "deny", "reason": "本番マイグレーションファイルの変更は禁止されています"}

ツールの実行を拒否し、理由をClaudeに伝えます。

{"updatedInput": {"command": "npm run lint -- --fix"}}

ツールへの入力を変更して実行します。元のコマンドを安全な代替コマンドに差し替える、といった使い方ができます。

PostToolUseでの制御:

{"suppressOutput": true}

ツールの出力がClaudeのコンテキストに含まれなくなります。

Stop/SubagentStopでの制御:

{"decision": "block", "reason": "ビルドエラーが検出されました。修正してください。"}

Claudeの停止を阻止し、追加の作業を指示します。

複数フックの設定例

1つのイベントに対して複数のmatcherとフックを設定できます。

{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "/path/to/validate-bash-command.sh" } ] }, { "matcher": "Write", "hooks": [ { "type": "command", "command": "/path/to/check-write-target.sh" } ] } ], "PostToolUse": [ { "matcher": "Edit", "hooks": [ { "type": "command", "command": "npx prettier --write \"$CLAUDE_TOOL_INPUT\" 2>/dev/null || true" } ] } ] } }

フックは定義された順序で実行されます。1つのフックが deny を返した場合、後続のフックは実行されずにツールの実行が拒否されます。

実践的なHookレシピ集

ここからは、実際のプロジェクトで使える具体的なフック設定を紹介します。私たちaduceが運用している構成をベースに、汎用性の高いレシピを厳選しました。

レシピ1: ファイル編集後の自動フォーマット

Claudeが生成するコードはおおむねフォーマットが整っていますが、プロジェクト固有のPrettier設定やESLint規約に完全に準拠しているとは限りません。PostToolUseフックでフォーマッタを自動実行すれば、この問題を恒久的に解消できます。

{ "hooks": { "PostToolUse": [ { "matcher": "Edit", "hooks": [ { "type": "command", "command": "node -e \"const input = JSON.parse(process.env.CLAUDE_TOOL_INPUT || '{}'); const file = input.file_path; if (file && (file.endsWith('.ts') || file.endsWith('.tsx') || file.endsWith('.js') || file.endsWith('.jsx'))) { require('child_process').execSync('npx prettier --write ' + file + ' && npx eslint --fix ' + file, {stdio: 'inherit'}); }\"" } ] }, { "matcher": "Write", "hooks": [ { "type": "command", "command": "node -e \"const input = JSON.parse(process.env.CLAUDE_TOOL_INPUT || '{}'); const file = input.file_path; if (file && (file.endsWith('.ts') || file.endsWith('.tsx') || file.endsWith('.js') || file.endsWith('.jsx'))) { require('child_process').execSync('npx prettier --write ' + file + ' && npx eslint --fix ' + file, {stdio: 'inherit'}); }\"" } ] } ] } }

このレシピのポイントは、EditWrite の両方のツールに対してフックを設定している点です。Claudeはファイルの変更に Edit を、新規作成に Write を使い分けるため、両方をカバーする必要があります。

レシピ2: 保護ディレクトリへの書き込みブロック

マイグレーションファイルや設定ファイルなど、手動管理すべきファイルへのClaude Codeによる変更を防止するフックです。

{ "hooks": { "PreToolUse": [ { "matcher": "Edit", "hooks": [ { "type": "command", "command": "node -e \"const input = JSON.parse(process.env.CLAUDE_TOOL_INPUT || '{}'); const file = input.file_path || ''; const protected_dirs = ['supabase/migrations', '.env', 'docker-compose.prod']; if (protected_dirs.some(d => file.includes(d))) { console.log(JSON.stringify({decision: 'deny', reason: file + ' は保護対象です。手動で変更してください。'})); } else { console.log(JSON.stringify({decision: 'allow'})); }\"" } ] }, { "matcher": "Write", "hooks": [ { "type": "command", "command": "node -e \"const input = JSON.parse(process.env.CLAUDE_TOOL_INPUT || '{}'); const file = input.file_path || ''; const protected_dirs = ['supabase/migrations', '.env', 'docker-compose.prod']; if (protected_dirs.some(d => file.includes(d))) { console.log(JSON.stringify({decision: 'deny', reason: file + ' は保護対象です。手動で変更してください。'})); } else { console.log(JSON.stringify({decision: 'allow'})); }\"" } ] } ] } }

このフックは、supabase/migrations 配下のファイル、.env ファイル、本番用Docker Compose設定への書き込みを一律で拒否します。CLAUDE.mdに「マイグレーションファイルを変更しないで」と書くだけでは不安が残る場合に、確実な防御策として機能します。

レシピ3: 危険なコマンドの実行ブロック

データベースのリセット、本番環境への直接デプロイ、rm -rf といった破壊的なコマンドの実行を防止します。

{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "node -e \"const input = JSON.parse(process.env.CLAUDE_TOOL_INPUT || '{}'); const cmd = input.command || ''; const dangerous = ['supabase db reset', 'rm -rf /', 'DROP DATABASE', 'git push --force origin main', 'git push --force origin prod']; const match = dangerous.find(d => cmd.includes(d)); if (match) { console.log(JSON.stringify({decision: 'deny', reason: '危険なコマンドが検出されました: ' + match})); }\"" } ] } ] } }

私たちのプロジェクトでは、CLAUDE.mdに「supabase db reset は絶対に実行しない」と記載していますが、Hooksでも同じルールを二重に適用しています。CLAUDE.mdは「Claudeへの依頼」、Hooksは「システムレベルでの強制」という二段構えの防御です。この冗長性は、データ消失のリスクを考えれば十分に正当化されます。

レシピ4: ツール呼び出しのログ記録

デバッグやチームでの知見共有のために、Claudeのすべてのツール呼び出しをログファイルに記録するフックです。

{ "hooks": { "PreToolUse": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "echo \"$(date -u '+%Y-%m-%dT%H:%M:%SZ') [PRE] tool=$CLAUDE_TOOL_NAME input=$(echo $CLAUDE_TOOL_INPUT | head -c 200)\" >> /tmp/claude-hooks.log" } ] } ], "PostToolUse": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "echo \"$(date -u '+%Y-%m-%dT%H:%M:%SZ') [POST] tool=$CLAUDE_TOOL_NAME\" >> /tmp/claude-hooks.log" } ] } ] } }

matcher に "*" を指定しているため、すべてのツール呼び出しが対象になります。ログファイルを定期的に確認することで、Claudeがどのような順序でツールを使い、何を実行しているかを把握できます。予期しない動作のデバッグや、ワークフローの改善にも役立ちます。

レシピ5: コミットメッセージのバリデーション

チームで定めたConventional Commits形式に準拠しているか、コミット時に自動検証するフックです。

{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "node -e \"const input = JSON.parse(process.env.CLAUDE_TOOL_INPUT || '{}'); const cmd = input.command || ''; if (cmd.includes('git commit')) { const msgMatch = cmd.match(/(?:-m\\s+['\\\"])(.*?)(?:['\\\"])/); if (msgMatch) { const msg = msgMatch[1]; const valid = /^(feat|fix|docs|style|refactor|test|chore|ci|perf|build)/.test(msg); if (!valid) { console.log(JSON.stringify({decision: 'deny', reason: 'コミットメッセージはConventional Commits形式にしてください(feat:, fix:, docs: など)'})); } } }\"" } ] } ] } }

Git hookの commit-msg でも同様のバリデーションは可能ですが、HooksはClaude Codeがコマンドを実行する「前」に検証する点で異なります。不正なコミットメッセージをClaude Codeが生成した場合、実行すらされずにフィードバックされるため、無駄なリトライを防げます。

レシピ6: Claudeにフックを書いてもらう

ここまでのレシピを自分で書くのが手間に感じた方もいるかもしれません。実は、Claude Code自身にフックを書いてもらうことも可能です。

Claude Codeのチャットで以下のように依頼するだけです。

「ファイル編集後にeslintを自動実行するHookを.claude/settings.jsonに設定して」

Claudeはsettings.jsonの構造を理解しており、適切なイベントタイプ・matcher・コマンドを含むフック設定を生成してくれます。複雑な条件分岐を含むフックでも、自然言語で要件を伝えればシェルスクリプトに変換してくれるため、シェルスクリプトに慣れていない方でもフックの恩恵を受けられます。

また、既存のフックの確認には /hooks コマンドが便利です。現在設定されているフックの一覧とその状態を確認できるため、設定のデバッグや棚卸しに活用できます。

HooksとSkillsの違い・使い分け

HooksとSkillsはどちらもClaude Codeの拡張機能ですが、設計思想が根本的に異なります。この違いを正確に理解することが、適切な使い分けの前提になります。

設計思想の違い

Skillsは「オンデマンドな知識・ワークフロー」です。ユーザーが /review/test-gen のようにスラッシュコマンドで明示的に呼び出し、Claudeに特定のコンテキストや指示を与えます。実行タイミングはユーザーが制御し、Claudeの応答を通じて間接的に効果を発揮します。

Hooksは「自動的かつ確実なアクション」です。特定のイベントが発生すると人間の介入なしに発火し、シェルスクリプトとして直接実行されます。Claudeの推論を経由しないため、確実性が保証されます。

この違いを別の言葉で表現すると、Skillsは「Claudeに教える」仕組みであり、Hooksは「Claudeを制御する」仕組みです。

使い分けの判断基準

以下の表に、判断の目安を整理します。

観点SkillsHooks
実行タイミングユーザーが明示的に呼び出すイベントに応じて自動実行
確実性確率的(LLMの解釈に依存)決定的(シェルスクリプト)
柔軟性高い(自然言語での指示)低い(事前定義のルール)
対象複雑なタスク、判断を伴う作業機械的な処理、ガードレール
設定ファイル.claude/skills/*.md.claude/settings.json

具体例で考えてみましょう。「コードレビューを行う」はSkillsに適しています。レビューの観点や深さは状況によって変わり、LLMの判断力が求められるためです。一方、「コードレビュー後にリンターを走らせる」はHooksに適しています。リンターの実行は機械的な処理であり、毎回確実に実行されるべきだからです。

組み合わせて使う

HooksとSkillsは排他的な関係ではなく、組み合わせることで真価を発揮します。

たとえば、テスト生成のSkill(/test-gen)でClaudeにテストコードを生成させつつ、PostToolUseのHookで生成されたテストファイルに自動的にフォーマッタを適用し、さらにStopのHookで npm test を実行してテストが通ることを確認する。このように、Skillsで「何をするか」を指示し、Hooksで「品質の担保」を自動化するのが理想的な構成です。

私たちaduceでは、CLAUDE.mdに記載するルール、Skillsで定義するワークフロー、Hooksで強制するガードレール、という3層構造で運用しています。CLAUDE.mdは「方針」、Skillsは「手順」、Hooksは「強制」という位置づけです。それぞれの強みを活かすことで、AI駆動の開発における信頼性と効率性を両立できています。

Plan Modeとの連携

Claude Codeには「Plan Mode」という動作モードがあります。通常モードでは、Claudeは要件を解釈して即座にコードを変更しますが、Plan Modeではまず変更計画を提示し、ユーザーの承認を得てから実行に移ります。

Hooksは、このPlan Modeと組み合わせることで、さらに堅牢な開発ワークフローを構築できます。

Plan Modeでの変更計画をHooksで検証する

たとえば、Plan Modeで提示された変更計画が特定の基準を満たしているかをHooksで自動検証するワークフローが考えられます。

{ "hooks": { "Stop": [ { "hooks": [ { "type": "command", "command": "node -e \"const resp = process.env.CLAUDE_STOP_RESPONSE || ''; if (resp.includes('TODO') || resp.includes('後で対応')) { console.log(JSON.stringify({decision: 'block', reason: 'TODOや先送り事項が含まれています。具体的な実装方針に置き換えてください。'})); }\"" } ] } ] } }

このフックは、Claudeが「TODO」や「後で対応」といった曖昧な記述を含む計画で停止しようとした場合に、それをブロックして具体化を促します。Plan Modeで計画を確認する際に、人間のレビューを補完する自動チェックとして機能します。

段階的な実行の制御

大規模な変更を行う場合、Plan Modeで全体計画を立て、段階的に実行するワークフローにHooksを組み込むことも有効です。各段階の完了後にStopフックで自動テストを実行し、テストが通った場合のみ次の段階に進む、といった制御が可能です。

{ "hooks": { "Stop": [ { "hooks": [ { "type": "command", "command": "npm test 2>/dev/null && echo '{\"decision\": \"allow\"}' || echo '{\"decision\": \"block\", \"reason\": \"テストが失敗しています。修正してから完了してください。\"}'" } ] } ] } }

このフックを設定しておくと、Claudeがタスクを完了しようとするたびに自動テストが走り、テストが失敗していればClaudeに修正を続けさせることができます。Plan Modeで「まず型定義を変更し、次にコンポーネントを更新し、最後にテストを修正する」という計画を立てた場合、各段階でテストが通ることを強制できるわけです。

実運用での注意点

HooksとPlan Modeの組み合わせは強力ですが、いくつかの注意点があります。

まず、Stopフックの実行コストに注意が必要です。npm test のような重い処理をStopフックに設定すると、Claudeが停止を試みるたびに実行されるため、ワークフロー全体の所要時間が増加します。テストスイートが大きいプロジェクトでは、変更されたファイルに関連するテストだけを実行するなどの工夫が求められます。

また、Stopフックで block を返しすぎると、Claudeが無限ループに陥る可能性があります。ブロック条件は現実的に達成可能なものに限定し、最大リトライ回数を設定するなどの安全弁を設けることをお勧めします。

まとめ

Claude Code Hooksは、CLAUDE.mdやSkillsとは異なる「確実性」という価値を提供する仕組みです。LLMの確率的な性質に依存しないシステムレベルの制御により、AI駆動の開発における信頼性を大幅に向上させます。

本記事で紹介した内容を振り返ると、重要なポイントは以下のとおりです。

  • Hooksはシェルスクリプトを特定のイベントで確実に実行する仕組みであり、CLAUDE.md(助言的)とは異なり決定的に動作する
  • PreToolUseはパーミッションチェックの前に発火するため、最も強力なガードレールとして機能する
  • settings.jsonにJSONで定義し、プロジェクト共有(.claude/settings.json)と個人用(~/.claude/settings.json)を使い分ける
  • フォーマッタの自動実行、保護ディレクトリの書き込みブロック、危険なコマンドの防止など、実践的なレシピが即座に活用できる
  • Skillsとは排他的ではなく、Skillsで「何をするか」を、Hooksで「品質の担保」を担当する組み合わせが効果的
  • Plan Modeと連携することで、変更計画の自動検証やテスト駆動の段階的実行が実現できる

Hooksの活用は、CLAUDE.mdによるルール定義、Skillsによるワークフロー自動化と合わせて、Claude Codeを本格的な開発パートナーとして機能させるための重要な要素です。まずは「ファイル編集後のフォーマッタ自動実行」のような簡単なフックから始めて、徐々にプロジェクトに合ったフックを追加していくことをお勧めします。

Claude Codeの導入や運用について相談されたい方は、お気軽にお問い合わせください。

参考文献