blog
AIブログ
Claude Code hooks(フック)の設定と活用ガイド【2026】
文:クリスタルメソッド編集部 監修:河合 継(代表取締役)
Claude Code Hooksとは何か:概要と位置づけ
Claude Code Hooks(フック)は、Claude Codeの実行サイクルの決まったタイミング(ツールを実行する直前・直後、セッションの開始、Claudeの応答の終了など)で、あらかじめ登録しておいたシェルコマンドやスクリプトを自動で実行する仕組みです。「Claudeがコードを書いた直後に自動で整形する」「危険なコマンドを実行前に止める」「ツール呼び出しをすべて記録する」「作業が終わったら通知する」といった自動化と安全装置を、設定ファイル(settings.json)に数行書くだけで組み込めます。公式ドキュメントは、Hooksを「決まった動作が必ず起きるようにする仕組み」と位置づけています。モデルの判断に任せるのではなく、決めたルールを確実に実行させるための機能です。
Claude Codeは、Anthropicの対話AI Claude(クロード)に、ファイルの編集やコマンドの実行まで任せられる、端末で動くコーディングエージェントです(全体像はClaude Codeとはの記事で解説しています)。本記事はそのうちHooksに絞り、仕組み・33種類のイベント・設定の書き方・実際にフックが発火する画面・デバッグ方法を、公式ドキュメント(2026年10月14日確認)と、この端末のClaude Code v2.1.299で実行した結果にもとづいてまとめます。フックは、端末・IDEの拡張機能・デスクトップアプリ・クラウドのどこで動かしても、同じイベントが発火します。
Git Hooksとの違い
名前が似ているGit Hooksは、git commitやgit pushといったGitの操作をきっかけに動く仕組みです。Claude Code Hooksは、Gitの操作ではなく、Claude Codeの実行サイクル(ツール呼び出しの前後、セッションの開始、Claudeの応答の終了など)をきっかけに動きます。そのため、コミットをしない作業の途中でも、Claudeがファイルを書き換えた直後に整形したり、危険なコマンドを実行前に止めたりできます。両者は置き換えの関係ではなく、併用できます。
Hooksが解決する問題:なぜ必要か
Claude Codeはエージェントとして自律的にコードを編集・実行します。その自律性は強力ですが、次のような課題が生まれます。
- 品質チェックの抜け漏れ:AIが生成したコードにlintやフォーマッタを毎回手動で実行するのは現実的ではありません
- セキュリティ・ガバナンス:特定のコマンド(本番DBへの直接操作など)を誤って実行させないための歯止めが必要です
- 可観測性の不足:AIが何をいつ実行したかを残す監査ログの仕組みは、初期状態では用意されていません
- チームルールの強制:個人の設定に依存せず、プロジェクト全体で一貫したルールを適用したい場面があります
Hooksはこれらを、シェルスクリプトという馴染みのある手段で解決します。Claude Code本体のコードを変更せず、外部コマンドを挟み込むだけでワークフローを制御できます。なお、「決まったツールやコマンドを常に禁止する」だけなら、スクリプトを書かずに済むpermissions設定のほうが向いています。両者の使い分けは、次の「Hooksとpermissions・CLAUDE.mdの使い分け」の節で整理します。
フックを組み合わせて作業を自動化する設計は、Claude Codeの自動化実装ガイドで解説しています。
Hooksとpermissions・CLAUDE.mdの使い分け
Hooksを入れるか迷う人の多くは、「permissions(許可・拒否のルール)やCLAUDE.md、サンドボックスで足りないのか」という点で止まります。先に結論を示すと、毎回必ず起きてほしい処理や、中身を見て止めたい判断はHooks、決まったツールやコマンドを常に禁止するだけならpermissions、方針や背景を伝えるだけならCLAUDE.md、シェルコマンドの影響範囲をOSの力で囲うならサンドボックスです。
| やりたいこと | 向いている仕組み | 理由 |
|---|---|---|
| 保存のたびに必ず整形する | Hooks(PostToolUse) | モデルの判断に任せず、毎回確実に実行できる |
| コマンドの中身を見て、条件つきで止める | Hooks(PreToolUse) | スクリプトで任意の判定ができ、止めた理由もClaudeに伝えられる |
| 特定のツールや決まったコマンドを常に禁止する | permissionsのdenyルール | スクリプトを書かずに、設定だけで拒否できる |
| 方針・背景・コーディング規約を伝える | CLAUDE.md | 従うかどうかはモデルの判断になるため、強制ではなく指針 |
| シェルコマンドが触れる場所と通信先を囲う | サンドボックス | OSが強制する隔離で、対象はシェルコマンド。コマンド型のフック自体は、この隔離の外で自分の権限のまま動く(公式ドキュメント 2026年10月27日確認) |
リスクの全体像と優先順位はClaude Codeのセキュリティの記事にまとめています。ここからは、HooksとpermissionsでPreToolUseのブロックがどう違うかを、もう少し具体的に見ます。
| 機能 | Hooks(PreToolUse exit 2) | permissions(denyルール) |
|---|---|---|
| ブロックの粒度 | コマンド内容・引数レベルで動的判定可能 | ツール名のほか、Bash(git push *)のようにコマンドのパターンでも指定できる(決め打ちのルール) |
| フィードバック | ブロック理由をモデルに伝えられる | 拒否されたことが伝わる(理由の文は書けない) |
| 柔軟性 | 高(スクリプトで任意ロジック実装) | 中(ルールの一覧。スクリプトのような任意の判定はできない) |
| 用途 | 条件付きブロック・パターンマッチング | 特定のツールや決まったコマンドの禁止 |
permissionsのルールは、Claude Codeの中で /permissions を実行すると一覧で確認できます。下の画面は、プロジェクトの .claude/settings.json に permissions.deny として Bash(git push *) と WebSearch を書き、Denyタブを開いたものです。表示にあるとおり、拒否したツールの要求は、Claude Codeが常に断ります。

/permissions のDenyタブ。Bash(git push *) と WebSearch が拒否ルールとして並んでいます(2026年10月12日に実際に実行して撮影。Claude Code v2.1.296。ログインしていない撮影用の環境)「Bashツールは使えるが、本番に関わるコマンドだけブロックしたい」という要件にはHooksが適しており、「WebSearchツール自体を一切使わせない」という要件にはpermissionsが適しています。両者を組み合わせた多層防御が実践的なアプローチです。なお、PreToolUseフックは権限モードの判定より前に動くため、フックのdenyは権限を省略するモードでも有効です。逆に、フックがallowを返しても、設定のdenyルールは上書きできません。
もう一つ、CLAUDE.mdとの違いも押さえておきます。公式ガイドは、Hooksを「決まった動作が必ず起きるようにする決定的な制御」と説明しています。CLAUDE.mdに書いた指示は、従うかどうかをモデルの判断に任せる形になります。「保存のたびに必ず整形する」「このファイルは必ず編集させない」のように、毎回確実に起きてほしいことはHooks、方針や背景を伝えたいだけならCLAUDE.md、という分け方が目安です(公式ドキュメント 2026年10月14日確認)。
次に読む:Claude Codeの権限設定はどうする?
まず動かしてみる:最初のフックを登録して、実際にブロックされる画面を見る
仕組みの説明に入る前に、いちばん効果の分かりやすい「危険なコマンドのブロック」を、この端末のClaude Code v2.1.299で実際に動かした結果を示します。手順は3つです。
- プロジェクトの
.claude/hooks/block_dangerous.shにスクリプトを置き、chmod +xで実行権限を付けます。スクリプトの中身は後の「危険なBashコマンドのブロック」の節に載せています(prod_dbやdrop tableを含むコマンドを、終了コード2で止めるものです) - プロジェクトの
.claude/settings.jsonに、PreToolUseイベント・matcherBashでそのスクリプトを登録します - Claude Codeを起動し、
/hooksで登録されていることを確認してから、止めたいコマンドを含む作業を頼みます
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block_dangerous.sh"
}
]
}
]
}
}
この状態で「prod_db_export.csv の中身を Bash の cat コマンドで表示して」と頼むと、Claudeが cat prod_db_export.csv を実行しようとした瞬間にPreToolUseフックが動き、コマンドに prod_db が含まれるため終了コード2で止まりました。下の画面がその結果です。フックが標準エラー出力に書いた「BLOCKED: …」の文がそのままClaudeに伝わり、Claudeは「プロジェクトのPreToolUseフックにブロックされた」と理由を説明したうえで、別の手段で回避しない、と答えています。

cat prod_db_export.csv を止めた画面。フックのメッセージ「BLOCKED: ‘prod_db’ を含むコマンドは…」がClaudeに伝わっています(2026年10月14日にこの端末で実際に実行して撮影。Claude Code v2.1.299。ウィンドウの枠は描画したもので、文字は実行結果そのままです)続けて「echo hello を Bash で実行して」と頼むと、こちらは禁止パターンに当たらないため、フックは終了コード0で何も言わず、通常どおり実行されて hello が出力されました。同じフックが「止めるべきものだけを止める」ことが分かります。

echo hello を頼んだ画面。禁止パターンに当たらないため、フックは何も言わずに通しています(2026年10月14日に実際に実行して撮影)prod_db を含むcatコマンドがフックに止められ、続けて echo hello は通る様子(約29秒・音声なし。2026年10月14日に実際に実行して録画。ウィンドウの枠は描画したもので、文字は実行結果そのままです)2つ、知っておきたい点があります。1つ目は、ブロックされたコマンドには実行の確認画面が出ていないことです。PreToolUseフックは権限の確認よりも前に動くため、確認画面に進む前に止まります(前の「Hooksとpermissions・CLAUDE.mdの使い分け」の節の最後でも触れています)。2つ目は、動画の最初に出ている「このフォルダを信頼するか」の確認です。対話モードでは、この確認に同意するまで設定ファイルに書いたフックは動きません。一方 claude -p のような非対話の実行では確認が出ず、フォルダは信頼済みとして扱われます(「セキュリティ上の注意」の節で触れます)。
Hooksの実行タイミング:ライフサイクルイベント一覧(公式は33種類)
公式ドキュメント(2026年10月14日確認)では、フックを差し込めるライフサイクルイベントが33種類定義されています。大きく分けると、セッションごとに1回(SessionStart・SessionEnd)、ターンごとに1回(UserPromptSubmit・Stop・StopFailure)、ツール呼び出しのたび(PreToolUse・PostToolUse)の3つの頻度があります。まずは実務でよく使う次の4つから押さえるのが近道です。
| イベント名 | 発火タイミング | 主なユースケース |
|---|---|---|
| PreToolUse | ツール実行の直前(ユーザー確認の前) | コマンドのブロック・検証・事前ログ |
| PostToolUse | ツール実行の直後(結果が返った後) | lint/テスト自動実行・変更後ログ |
| Notification | Claude Codeが通知を送るとき | Slack通知・デスクトップ通知連携 |
| Stop | Claudeが応答を終了するとき | 完了通知・後処理・サマリー生成 |
まず試すなら、PostToolUseとPreToolUseの組み合わせが基本形です。PostToolUseでファイル保存後の自動フォーマットを実行し、PreToolUseで危険なコマンドを遮断します。
33種類のイベント一覧:発火タイミングと、終了コード2で止められるか
すべてのイベントを、公式リファレンスの順(セッションの準備→エージェントのループ→セッションの終了)に並べます。「止められるか」は、フックが終了コード2で終了したときに、その動作を止められるかどうかです。まだ起きていない動作(ツール呼び出し・プロンプトの処理・圧縮など)は止められ、すでに起きたことや通知は止められません(公式リファレンス 2026年10月14日確認)。
| イベント名 | 発火タイミング | 終了コード2で止められるか |
|---|---|---|
| SessionStart | セッションの開始・再開時(matcherは始まり方:startup・resume・clear・compact・fork) | 止められない(stderrはユーザーに表示) |
| Setup | --init-only、または -p モードの --init/--maintenance で起動したとき(CIなどの一回限りの準備) | 無視される |
| UserPromptSubmit | プロンプトを送信し、Claudeが処理する前 | 止められる(プロンプトがClaudeに届かない) |
| UserPromptExpansion | 入力したコマンドがプロンプトに展開され、Claudeに届く前 | 止められる(展開を止める) |
| PreToolUse | ツール呼び出しの直前 | 止められる(ツール呼び出しを中止) |
| PermissionRequest | ツール呼び出しに権限の判断が必要なとき | 無視される(拒否はJSONの decision で) |
| PermissionDenied | autoモードがツール呼び出しを拒否したとき | 無視される(JSONの retry で再試行を促せる) |
| PostToolUse | ツール呼び出しが成功した後 | 止められない(stderrをClaudeに表示) |
| PostToolUseFailure | ツール呼び出しが失敗した後 | 止められない(stderrをClaudeに表示) |
| PostToolBatch | 並列のツール呼び出しが一括で終わり、次のモデル呼び出しに進む前 | 止められる(ループを止める) |
| Notification | Claude Codeが通知を出すとき(matcherは通知の種類:permission_prompt・idle_prompt など) | 無視される |
| MessageDisplay | Claudeの応答文を画面に表示するとき | 止められない(元の文を表示) |
| SubagentStart | サブエージェントの起動時 | 止められない(stderrはユーザーに表示) |
| SubagentStop | サブエージェントの終了時 | 止められる(終了させない) |
| TaskCreated | タスクが作られるとき | 止められる(作成を取り消す) |
| TaskCompleted | タスクが完了扱いになるとき | 止められる(完了にしない) |
| Stop | Claudeが応答を終えるとき | 止められる(作業を続けさせる) |
| StopFailure | APIエラーでターンが終わったとき | 無視される |
| TeammateIdle | エージェントチームのメンバーが待機に入る直前 | 止められる(作業を続けさせる) |
| InstructionsLoaded | CLAUDE.md や .claude/rules/*.md が読み込まれたとき | 無視される |
| ConfigChange | セッション中に設定ファイルが変わったとき | 止められる(変更を反映しない。組織のpolicy設定を除く) |
| CwdChanged | 作業ディレクトリが変わったとき | 止められない |
| DirectoryAdded | /add-dir などで作業ディレクトリが追加されたとき | 止められない |
| FileChanged | 監視対象のファイルが変わったとき(matcherに監視するファイル名を書く) | 止められない |
| WorktreeCreate | worktreeが作られるとき | 0以外の終了コードで作成が失敗する |
| WorktreeRemove | WorktreeCreateで作ったworktreeを削除するとき | 0以外の終了コードで削除が失敗する |
| PreCompact | コンテキスト圧縮の前 | 止められる(圧縮を止める) |
| PostCompact | コンテキスト圧縮の後 | 止められない |
| PreModelSwitch | モデルの切り替えを適用する前 | 止められる(切り替えを止める) |
| PostModelSwitch | モデルが切り替わった後 | 止められない |
| Elicitation | MCPサーバーがユーザーの入力を求めたとき | 止められる(要求を断る) |
| ElicitationResult | ユーザーが応答し、サーバーへ返す前 | 止められる(応答を断る扱いになる) |
| SessionEnd | セッションの終了時 | 止められない |
matcherで絞り込める値はイベントごとに違います。PreToolUseなどのツール系イベントはツール名、SessionStartは始まり方、Notificationは通知の種類、SubagentStart/SubagentStopはエージェントの種類で絞ります。Stop・UserPromptSubmit・CwdChanged などはmatcherに対応しておらず、書いても無視されて毎回発火します。
Hooksの設定場所:settings.jsonの構造
HooksはClaude Codeの設定ファイル(settings.json)に記述します。設定ファイルには適用スコープによって3つの場所があります。
設定ファイルを開かなくても、Claude Codeの中で /hooks を実行すると、登録されているフックを確認できます。一覧からフックを1つ選ぶと、下の画面のように、どのイベントのどのmatcherのフックか、設定の置き場所(Source)、実行されるコマンドが表示されます。この画面の「Source」が、下の表の「プロジェクトローカル」(.claude/settings.json)に当たります。メニューは閲覧専用で、フックの追加や変更は settings.json を編集するか、Claudeに頼んで行います。

/hooks で登録済みのフックを選んだ画面(2026年10月12日に実際に実行して撮影。Claude Code v2.1.296。ログインしていない撮影用の環境)| スコープ | ファイルパス | 適用範囲 |
|---|---|---|
| ユーザーグローバル | ~/.claude/settings.json |
そのユーザーの全プロジェクト |
| プロジェクトローカル | {project}/.claude/settings.json |
そのプロジェクトのみ(git管理推奨) |
| プロジェクトローカル(個人) | {project}/.claude/settings.local.json |
そのプロジェクト内の個人設定(gitignore推奨) |
チーム開発では.claude/settings.jsonをgitリポジトリにコミットすることで、全メンバーに同一のHooksルールを自動適用できます。個人の追加設定はsettings.local.jsonに分離する運用が扱いやすいです。
そのほかの置き場所:プラグイン・スキル・サブエージェント・組織の設定
上の3つ以外にも、フックを置ける場所があります(公式ドキュメント 2026年10月14日確認)。置き場所によって、有効になる範囲が変わります。
| 置き場所 | 有効になる範囲 | 共有 |
|---|---|---|
| 組織の管理ポリシー設定 | 組織全体 | 管理者が配布 |
プラグインの hooks/hooks.json | プラグインを有効にしている間 | プラグインと一緒に配布 |
| スキルのfrontmatter | スキルを呼び出したあと、そのセッションの残り | スキルのファイルに定義 |
| サブエージェントのfrontmatter | そのサブエージェントが動いている間 | サブエージェントのファイルに定義 |
すべてのフックを止めたいときは、設定ファイルに "disableAllHooks": true を書きます。複数の設定ファイルがある場合は、優先順位を適用したあとの値が使われるため、プロジェクトの設定ファイルがユーザー設定の値を上書きすることがあります。
settings.jsonの基本スキーマ
Hooksの設定は"hooks"キー配下に記述します。基本的な構造は以下のとおりです。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write --ignore-unknown"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block_dangerous.sh"
}
]
}
]
}
}
設定の主要要素を整理すると次のようになります。
- matcher:どのツール名(またはツール名パターン)に反応するかを指定します。
"Bash"のように英数字と|だけなら完全一致、それ以外の文字を含むと正規表現として扱われます(詳しくは「設定時の注意点」の節) - hooks配列:マッチしたときに実行するハンドラー群(複数指定可)
- type:ハンドラーの種類。シェルコマンドを実行する
"command"がもっともよく使われます(ほかにhttp・mcp_tool・prompt・agentがあります) - command:実行するシェルコマンド文字列。
args(配列)を付けると、シェルを介さずに実行ファイルを直接起動する形になり、パスに空白があっても引用符で悩まずに済みます - if(任意):
"Bash(git *)"や"Edit(*.ts)"のように、権限ルールと同じ書き方で発火する条件をさらに絞ります。1つのハンドラーに書けるルールは1つです - timeout(任意):打ち切るまでの秒数。
command型の既定は600秒(UserPromptSubmitは30秒) - async(任意):
trueにすると、Claudeを待たせずにバックグラウンドで実行します(その場合、ブロックはできません) - onFailure(任意):フックが失敗したとき(起動できない・タイムアウト・0でも2でもない終了コード)に動作を止めるなら
"block"。既定は"continue"で、失敗しても動作は通ります(Claude Code v2.1.295以降) - statusMessage(任意):フックの実行中にスピナーへ表示する文
設定ファイルを編集すると、通常は自動で読み込まれます。数秒たっても/hooksに出ないときは、JSONに末尾のカンマやコメントが混ざっていないかを確かめ、それでも出なければセッションを再起動します。
入力データ:Hooksスクリプトが受け取る情報(標準入力のJSONと環境変数)
フックが発火すると、Claude Codeはそのイベントの情報をJSON形式で標準入力(stdin)に渡します。スクリプトはこのJSONを読み取って、「どのファイルが編集されたか」「どのコマンドが実行されようとしているか」を判断します。CLAUDE_TOOL_INPUT_FILE_PATHのような環境変数で渡されるわけではない点に注意してください(公式ドキュメント 2026年10月14日確認)。JSONの読み取りにはjqを使うのが一般的です(macOSはbrew install jq、Debian/Ubuntuはapt-get install jq)。
たとえばClaudeがnpm testを実行しようとしたとき、PreToolUseのフックには次のJSONが渡されます(公式ドキュメントの例)。
{
"session_id": "abc123",
"cwd": "/Users/sarah/myproject",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}
| 項目(jqでの取り出し方) | 内容 | 渡されるイベント |
|---|---|---|
.session_id |
セッションの一意ID(ログ用途に有用) | 全イベント |
.cwd |
イベント発生時の作業ディレクトリ | 全イベント |
.hook_event_name |
発火したイベント名(例:PreToolUse) |
全イベント |
.transcript_path |
会話の記録ファイル(JSONL)のパス。書き込みは非同期のため、直前のやりとりがまだ入っていないことがある | 全イベント |
.permission_mode |
そのときの権限モード(default・plan・acceptEdits・auto・dontAsk・bypassPermissions) |
全イベント |
.tool_name |
ツール名(例:Write、Bash) |
PreToolUse / PostToolUse など |
.tool_input.file_path |
Edit・Writeツールが操作するファイルのパス | PreToolUse / PostToolUse(Edit・Write) |
.tool_input.command |
Bashツールで実行されるコマンド文字列 | PreToolUse / PostToolUse(Bash) |
.tool_response |
ツールが返した結果。中身の形はツールごとに違う(Writeなら filePath と type) |
PostToolUse |
.tool_use_id |
ツール呼び出しごとのID。PreToolUseとPostToolUseの記録を結びつけるときに使う | PreToolUse / PostToolUse |
.duration_ms |
ツールの実行にかかった時間(ミリ秒)。許可の確認やPreToolUseフックの時間は含まない | PostToolUse(省略されることがある) |
.prompt |
送信されたプロンプトの文章 | UserPromptSubmit |
環境変数 $CLAUDE_PROJECT_DIR |
プロジェクトのディレクトリのパス。スクリプトの場所を絶対パスで書かずに済む | 全イベント |
実行後に動くPostToolUseでは、ツールに渡した引数(tool_input)に加えて、ツールが返した結果(tool_response)も届きます。Writeツールで新しいファイルを作ったときの入力は、公式ドキュメントでは次のように示されています(2026年10月28日確認)。保存後に整形するフックは、ここの tool_input.file_path を読んで対象のファイルを決めます。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-....jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PostToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.txt",
"content": "file content"
},
"tool_response": {
"filePath": "/path/to/file.txt",
"type": "create"
},
"tool_use_id": "toolu_01ABC123...",
"duration_ms": 12
}
ファイルを扱うツールの file_path は、いつも絶対パスで届きます(Windowsでは区切りが \)。また、PostToolUseは「Claude Codeのツールが成功したとき」だけ動きます。Bashのコマンドや別のプログラムが同じファイルを書き換えても、Edit|Write に合わせたフックは動きません。ファイルが変わったこと自体をきっかけにしたいときは、FileChangedイベントを使います。
スクリプトの中では、INPUT=$(cat)で標準入力を受け取り、echo "$INPUT" | jq -r '.tool_input.command'のように必要な項目を取り出します。イベントごとに渡される項目は異なるため、詳しくは公式のHooksリファレンスを確認してください。
Hooksの終了コードとClaude Codeの制御フロー
Hooksスクリプトの終了コード(exit code)は、Claude Codeの動作に直接影響します。この仕組みを理解することが、ガードレール実装の核心です。
正常終了。処理を続行する。
ブロックしないエラー。処理は続行する。画面の記録に「hook error」の通知とstderrの1行目が表示されるが、Claudeには伝わらない。
ブロック。stderrの内容を理由としてClaudeに伝える。止められるのはPreToolUse・UserPromptSubmit・Stopなど、まだ起きていない動作を表すイベントのみ。
終了コード2がブロック機能です。PreToolUseのフックが2を返すと、Claude Codeはツールの実行を中止し、スクリプトの標準エラー出力(stderr)をエラー理由としてモデルに伝えます。なお公式ドキュメント(2026年10月14日確認)では、PostToolUseなど既に実行済みの動作を表すイベントでは2を返しても止められず、PermissionRequestでは2が無視されると明記されています。モデルはそのフィードバックを受けて別の方法を検討します。
つまずきやすいのは、終了コード1とフックの失敗の扱いです。1はUnixの慣習では「失敗」ですが、Hooksでは「ブロックしないエラー」で、処理はそのまま通ります。方針を強制したいなら、必ず2を返してください。また、スクリプトが見つからない・実行権限がない・タイムアウトした場合も、既定では「ブロックしないエラー」として扱われ、ツール呼び出しはそのまま進みます(タイムアウトしたフックは出力ごと捨てられます)。ガードレール用のフックで「失敗したときも止めたい」なら、フックの設定に "onFailure": "block" を付けます(Claude Code v2.1.295以降。既定は "continue")。
終了コードよりも細かく制御する:JSON出力(permissionDecision)
終了コードでできるのは「ブロックする」か「何も言わない」かの2択です。もう少し細かく制御したいときは、終了コード0で終了し、標準出力にJSONを書きます(公式ドキュメント 2026年10月14日確認)。たとえばPreToolUseのフックは、ツール呼び出しを拒否してClaudeに理由を伝えることも、ユーザーに確認を求める形にすることもできます。
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "grepではなくrgを使ってください"
}
}
permissionDecisionには、次の値を指定できます。
- “allow”:対話的な許可の確認を省略します。ただし、設定のdenyルールとaskルール(組織の管理設定にあるものを含む)は、そのまま有効です
- “deny”:ツール呼び出しを取りやめ、
permissionDecisionReasonの内容をClaudeに伝えます - “ask”:通常どおり、ユーザーに許可の確認を表示します
注意点が2つあります。1つ目は、終了コード2でブロックする方法とJSONで返す方法は、1つのフックの中でどちらか一方に決めることです。2つ目は、JSONはイベントごとに形が違うことです。PostToolUseとStopは最上位の "decision": "block"、PermissionRequestは hookSpecificOutput.decision.behavior を使います。UserPromptSubmitでClaudeの文脈に文を足したいときは、additionalContextを hookSpecificOutput の中に入れます。最上位に書くと無視されます。
JSONで返せる判断の項目は、イベントごとに次のように決まっています(公式リファレンス「Decision control」2026年10月14日確認)。どのイベントでも使える共通の項目として、"continue": false(Claudeの処理を完全に止める。stopReasonにユーザーへ見せる文を書く)とsystemMessage(ユーザーへの警告表示)があります。
| イベント | 判断の書き方 | 主な項目 |
|---|---|---|
| PreToolUse | hookSpecificOutput の中 | permissionDecision(allow・deny・ask・defer)、permissionDecisionReason、updatedInput(ツールの引数を書き換える)、additionalContext |
| PermissionRequest | hookSpecificOutput.decision の中 | behavior(allow・deny)、updatedInput、updatedPermissions(許可ルールを追加する)、message(denyの理由) |
| PostToolUse/PostToolUseFailure | 最上位の decision | "decision": "block" と reason(結果の横にClaudeへの指摘を添える)、additionalContext、updatedToolOutput(Claudeに見せる結果を差し替える) |
| UserPromptSubmit | 最上位の decision | "decision": "block" と reason(プロンプトを止める)、hookSpecificOutput.additionalContext(文脈を足す)、sessionTitle |
| Stop/SubagentStop | 最上位の decision | "decision": "block" と reason(必須。続ける理由)、hookSpecificOutput.additionalContext(エラー扱いにしない指示) |
| PreCompact/ConfigChange/PostToolBatch | 最上位の decision | "decision": "block" と reason |
| SessionStart/SubagentStart/PostModelSwitch | 文脈の追加だけ | hookSpecificOutput.additionalContext(止めることはできない) |
| Notification/SessionEnd/StopFailure/PostCompact など | 判断の項目なし | 記録・通知・後処理のためのイベント。出力は読まれない |
additionalContext・systemMessageと、JSONでない標準出力の文は、それぞれ1万字で切られ、超えた分はファイルに書き出されます。Claudeに必ず読ませたい内容は、この上限に収めてください。
JSONの組み立てには、文字列をつなげるのではなく jq を使うのが安全です。引用符やバックスラッシュが正しくエスケープされ、「標準出力がJSONとして読めない」というエラーを避けられます。
実践的な設定例5選
ファイル保存後の自動フォーマット(Prettier)
最もシンプルで効果の高い設定です。ClaudeがファイルをWriteまたはEditするたびにPrettierを自動実行します。標準入力のJSONからjqで編集されたファイルのパスを取り出し、Prettierに渡しています(公式ドキュメントの例に、Prettierが対応していない種類のファイルを無視する--ignore-unknownを付けたもの)。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write --ignore-unknown"
}
]
}
]
}
}
この設定をユーザーグローバル設定(~/.claude/settings.json)に書くと、全プロジェクトで有効になります。Claudeが書いたコードのフォーマットを毎回手で直す手間を減らせます。
JavaScript以外の言語も、拡張子で整形コマンドを分ける
Prettierが扱うのはJavaScript・TypeScript・CSS・Markdownなどです。ほかの言語は、それぞれの言語の整形ツールを同じPostToolUseで呼びます。1つのスクリプトで拡張子を見て分けると、設定は1行で済みます。各ツールは、あらかじめその言語の開発環境に入れておく必要があります。
| 言語(拡張子) | 整形コマンドの例 | 補足 |
|---|---|---|
| Rust(.rs) | rustfmt "$FILE" | Rustの標準の整形ツール |
| Go(.go) | gofmt -w "$FILE" | -w でファイルを上書き |
| Python(.py) | black "$FILE" | lintも走らせる例は後の節 |
| Ruby(.rb) | rubocop -a "$FILE" | -a は安全な自動修正だけ |
| Java(.java) | google-java-format -i "$FILE" | -i でファイルを上書き |
| C / C++(.c .cpp .h) | clang-format -i "$FILE" | 設定はプロジェクトの .clang-format |
拡張子で分けるスクリプトの例です。.claude/hooks/format.sh として保存し、PostToolUseの Edit|Write に "$CLAUDE_PROJECT_DIR"/.claude/hooks/format.sh を登録します。
#!/bin/bash FILE=$(jq -r '.tool_input.file_path') case "$FILE" in *.rs) rustfmt "$FILE" ;; *.go) gofmt -w "$FILE" ;; *.py) black -q "$FILE" ;; *.rb) rubocop -a "$FILE" >/dev/null ;; *.java) google-java-format -i "$FILE" ;; *.c|*.cpp|*.h) clang-format -i "$FILE" ;; *) npx prettier --write --ignore-unknown "$FILE" ;; esac exit 0
整形ツールが入っていない言語のファイルでも、作業が止まらないように最後は exit 0 で終えています。整形は「失敗しても作業を止めない」補助なので、止める判断はPreToolUseのブロック側に任せます。
危険なBashコマンドのブロック
本番環境に関連するコマンドや破壊的な操作を自動でブロックするスクリプトです。
#!/bin/bash
# .claude/hooks/block_dangerous.sh
# 標準入力のJSONから、これから実行されるコマンドを取り出す
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' | tr '[:upper:]' '[:lower:]')
# 禁止パターン定義
BLOCKED_PATTERNS=(
"rm -rf /"
"drop table"
"drop database"
"prod_db"
"curl.*| *bash"
)
for pattern in "${BLOCKED_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -q "$pattern"; then
# 理由は標準エラー出力(stderr)へ。終了コード2で実行を止めると、この文がClaudeに伝わる
echo "BLOCKED: '$pattern' を含むコマンドは自動実行できません。必要なら人が内容を確認して手動で実行してください。" >&2
exit 2
fi
done
exit 0
ブロックの理由は標準出力ではなく標準エラー出力(>&2)に書きます。終了コード2で止めたとき、Claudeに伝わるのはstderrの内容だからです。スクリプトはプロジェクトの.claude/hooks/に置き、chmod +xで実行権限を付けます。設定ファイルへの組み込みは以下のとおりです。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block_dangerous.sh"
}
]
}
]
}
}
ブロックのスクリプトを、見本のJSONで実際に動かして確かめる
Claudeに実行させなくても、フックのスクリプトは、標準入力にJSONを渡せば単体で動かせます。上の block_dangerous.sh に、公式ドキュメントの例と同じ npm test のJSONと、DROP TABLE を含むコマンドのJSONを渡した結果が次の動画です。npm test は終了コード0(通過)、DROP TABLE を含むコマンドは「BLOCKED」のメッセージが標準エラー出力に出て、終了コード2(ブロック)になります。続けて、audit_log.sh に同じJSONを渡すと、日時・セッションID・ツール名・コマンドが1行で記録されます。
block_dangerous.sh の終了コードと audit_log.sh の記録を確かめる様子(約12秒・音声なし。2026年10月12日に実際に実行して撮影。ウィンドウの枠は描画したもので、表示されている文字は実行結果そのままです)この確かめ方は、Claude Codeを起動しなくてもできるため、設定ファイルに登録する前にスクリプトだけを試すときに便利です。なお、この画面はClaude Codeにツールを使わせてフックが発火する場面ではなく、スクリプトに見本の入力を渡して動かしたものです。実際にフックが発火したときは、ブロックの理由がClaudeに伝わります。
Python ファイルの自動 lint(flake8 + black)
#!/bin/bash # .claude/hooks/py_lint.sh FILE_PATH=$(jq -r '.tool_input.file_path // empty') # Python以外のファイルは何もしない [[ "$FILE_PATH" == *.py ]] || exit 0 black -q "$FILE_PATH" if ! RESULT=$(flake8 "$FILE_PATH"); then # 指摘内容をstderrに出して終了コード2で返すと、Claudeに伝わる echo "$RESULT" >&2 exit 2 fi exit 0
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/py_lint.sh"
}
]
}
]
}
}
flake8が指摘を出した場合、その内容をstderrに書いて終了コード2で返しています。PostToolUseは実行済みの操作なので取り消しはできませんが、終了コード2のときはstderrがClaudeに表示されるため、Claudeが指摘を読んで次のステップで修正できます。flake8の終了コード(1)のまま終わらせると「ブロックしないエラー」として扱われ、指摘はClaudeに伝わりません。
実行ログの記録
コンプライアンス要件やデバッグのために、Claude Codeが実行したすべてのツール呼び出しをログファイルに記録します。
#!/bin/bash
# .claude/hooks/audit_log.sh
INPUT=$(cat)
LOG_FILE="${HOME}/.claude/audit.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "$INPUT" | jq -r --arg ts "$TIMESTAMP" \
'"\($ts) | SESSION:\(.session_id) | TOOL:\(.tool_name) | FILE:\(.tool_input.file_path // "N/A") | CMD:\(.tool_input.command // "N/A")"' >> "$LOG_FILE"
exit 0
{
"hooks": {
"PreToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/audit_log.sh"
}
]
}
]
}
}
matcherに"*"を指定する(または省略する)と全ツールをキャッチします。監査の要件があるプロジェクトでは、このログを毎日アーカイブするcronジョブと組み合わせる運用が考えられます。
タスク完了時のSlack通知(Stopイベント)
長時間かかるエージェントタスクが完了したとき、SlackのWebhookに通知します。席を離れている間に実行させておき、完了したら知らせる運用に有用です。
#!/bin/bash
# .claude/hooks/notify_slack.sh
INPUT=$(cat)
WEBHOOK_URL="${SLACK_CLAUDE_WEBHOOK_URL}" # 環境変数で管理
PROJECT=$(basename "$CLAUDE_PROJECT_DIR")
SESSION=$(echo "$INPUT" | jq -r '.session_id')
TIMESTAMP=$(date '+%H:%M:%S')
# 通知文はjqで組み立てる(引用符などが正しくエスケープされる)
PAYLOAD=$(jq -n --arg t "Claude Code の応答が完了 [${PROJECT}] ${TIMESTAMP} (Session: ${SESSION})" '{text: $t}')
curl -s -X POST "$WEBHOOK_URL" -H 'Content-type: application/json' --data "$PAYLOAD" > /dev/null
exit 0
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/notify_slack.sh"
}
]
}
]
}
}
Stopイベントはmatcherに対応していないため、matcherは書きません(毎回発火します)。Stopは作業全体の完了時だけでなく、Claudeが応答を終えるたびに発火する点にも注意してください。ユーザーが途中で中断したときは発火せず、APIエラーでターンが終わったときは、StopではなくStopFailureが発火します(公式ドキュメント 2026年10月14日確認)。入力を待っているときだけ通知したい場合は、Notificationイベントが向いています。WebhookURLはコード中に直書きせず、必ず環境変数経由で渡すようにしてください。
公式ガイドにある定番レシピ:ファイル保護・通知・compact後の再注入・権限の自動承認
ここまでの5つの設定例のほかに、公式のHooksガイドに載っている定番の型を4つ紹介します(公式ドキュメント 2026年10月14日確認)。
.envなどの保護対象ファイルへの編集をブロックする
.env、package-lock.json、.git/の中のファイルなどを、Claudeに編集させたくない場合の型です。PreToolUseでEdit|Writeに反応させ、標準入力のJSONの tool_input.file_path が保護対象のパターンを含んでいたら、終了コード2で止めます。止めた理由は標準エラー出力に書くので、Claudeはそれを読んで別のやり方を考えます。
#!/bin/bash
# .claude/hooks/protect-files.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Windowsの区切り文字(バックスラッシュ)を「/」にそろえてから照合する
FILE_PATH="${FILE_PATH//\\//}"
PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
exit 2
fi
done
exit 0
登録は、これまでの例と同じ形です。chmod +xで実行権限を付けるのを忘れないでください。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}
このスクリプトに、.envを指すJSONとsrc/index.tsを指すJSONを順に渡した結果が次の画面です。.envのときは「Blocked」のメッセージが標準エラー出力に出て終了コード2(ブロック)、src/index.tsのときは何も出ずに終了コード0(そのまま続行)になりました。

protect-files.sh に見本のJSONを渡して終了コードを確かめた画面(2026年10月14日に実際に実行して撮影。ウィンドウの枠は描画したもので、表示されている文字は実行結果そのままです。Claude Codeにツールを使わせてフックが発火する場面ではなく、スクリプト単体の実行です)入力待ちになったらデスクトップ通知を出す(Notification)
席を離れている間にClaudeが許可や入力を待っていることに気づけるように、Notificationイベントにデスクトップ通知のコマンドを登録する型です。公式ガイドでは、OSごとの通知コマンドが紹介されています。次はLinuxの例で、~/.claude/settings.jsonに書きます。
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
}
]
}
]
}
}
macOSではosascriptで通知を出します(Script Editorに通知の許可がないと表示されません)。Linuxのnotify-sendは、デスクトップの通知サービスが動く環境が前提で、ヘッドレスのサーバー、SSH接続、多くのコンテナでは出ません。先にコマンド単体で動作を確かめてください。
compact後に、重要な文脈をもう一度渡す(SessionStart)
会話が長くなると、Claude Codeは文脈を要約して空きを作ります(compact)。そのとき細かい取り決めが抜け落ちることがあります。SessionStartイベントに compact というmatcherを付けると、compactのあとに毎回、コマンドの標準出力をClaudeの文脈に足せます。
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo 'Reminder: use Bun, not npm. Run bun test before committing. Current sprint: auth refactor.'"
}
]
}
]
}
}
毎回同じ文脈を渡したいだけなら、HooksではなくCLAUDE.mdのほうが向いています(公式ガイドの案内)。echoの部分は、git log --oneline -5のように状態を出すコマンドにも置き換えられます。
特定の許可の確認を自動で承認する(PermissionRequest)
いつも許可している操作の確認画面を省略する型です。公式ガイドの例は、プランを出し終えたときにClaudeが呼ぶ ExitPlanMode だけを自動で承認します。ここまでの終了コードの例と違い、承認するにはフックが標準出力にJSONで判断を書く必要があります。
{
"hooks": {
"PermissionRequest": [
{
"matcher": "ExitPlanMode",
"hooks": [
{
"type": "command",
"command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'"
}
]
}
]
}
}
matcherはできるだけ狭くしてください。.*にしたり空のままにしたりすると、ファイルの書き込みやシェルコマンドを含む、すべての許可の確認を自動で承認してしまいます(公式ガイドの注意)。
Claude Code・AIエージェントの業務導入をご検討の方は、自社での開発実例を公開しているクリスタルメソッドの無料相談をご利用ください。
指示のたびに、今のブランチ名をClaudeに伝える(UserPromptSubmit)
UserPromptSubmitは、指示を送った直後、Claudeが読み始める前に動きます。終了コード0で標準出力に書いた文字は、Claudeへの追加の文脈として渡されます(画面の会話には表示されません)。次の例は、指示のたびに今のGitのブランチ名を伝えるものです。
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "echo \"現在のブランチ: $(git branch --show-current 2>/dev/null)\""
}
]
}
]
}
}
UserPromptSubmitのフックは、ほかのイベントより短い30秒が既定のタイムアウトで、終わるまでClaudeは指示を読み始めません。重い処理は置かないようにします。届いたかどうかは、デバッグログで確かめます(公式ドキュメント 2026年10月28日確認)。
サブエージェントやチームの仕事の終わりに確かめる:SubagentStop・TaskCompleted・TeammateIdle
サブエージェントやAgent Teamsを使うと、Claude本体の外で仕事が終わる場面が増えます。そこで使うのが次の3つのイベントです。
| イベント | 動くとき | 終了コード2を返すと |
|---|---|---|
| SubagentStop | サブエージェントが返答を終えたとき。matcherはエージェントの種類 | サブエージェントが止まらずに作業を続ける |
| TaskCompleted | タスクが「完了」にされるとき(matcherなし) | 完了にならず、標準エラー出力の文がモデルに伝わる |
| TeammateIdle | Agent Teamsの仲間が待機に入る直前(matcherなし) | 待機せず、伝えた内容をもとに作業を続ける |
公式ドキュメントにある例をもとにした、テストが通るまでタスクを完了にさせないスクリプトです。TaskCompletedに登録します。
#!/bin/bash INPUT=$(cat) TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject') if ! npm test 2>&1; then echo "テストが通っていません。直してから完了にしてください: $TASK_SUBJECT" >&2 exit 2 fi exit 0
SubagentStopの入力には、サブエージェントの最後の返答(last_assistant_message)とエージェントの種類(agent_type)が入るため、「調査役のサブエージェントの報告を記録に残す」といった使い方もできます。なお、プロンプトの候補表示など、Claude Code自身の内部の処理が終わったときもSubagentStopは動きます。種類を名指ししたmatcherにしておくと、こうした内部の処理には反応しません(公式ドキュメント 2026年10月28日確認)。サブエージェントそのものの作り方はサブエージェントの記事で解説しています。
複数イベント・複数コマンドの組み合わせ設定
実際の運用設定ファイルでは、複数のイベントと複数のhooksを組み合わせることになります。以下は、ここまでに紹介したフックを組み合わせたプロジェクト設定の例です。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/audit_log.sh"
},
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block_dangerous.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/audit_log.sh"
},
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write --ignore-unknown"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/notify_slack.sh"
}
]
}
]
}
}
同じイベントに複数のhooksがマッチした場合、それらは並列に実行され、すべてが最後まで実行されます。1つがブロック(deny)を返しても、ほかのフックの実行は止まりません。全部が終わったあとで結果がまとめられ、PreToolUseの判定はいちばん厳しいもの(deny)が優先されます。そのため「先のフックがブロックしたら、後ろのフックは動かない」という前提で設計しないでください(公式ドキュメント 2026年10月14日確認)。
この例を .claude/settings.json に書いて /hooks を実行すると、5つのフックが3つのイベントの下に並んで表示されます(PreToolUseのBash用に2つ、PostToolUseのEdit|Write用に2つ、Stopに1つ)。同じイベントのフックは、settings.json に書いた順に一覧に出ます。

/hooks を実行した画面。「5 hooks on 3 events」と表示され、イベントごとにフックが並びます(2026年10月12日に実際に実行して撮影。Claude Code v2.1.296。ログインしていない撮影用の環境)command型以外のハンドラー:http・mcp_tool・prompt・agent
ここまでの例はすべて、シェルコマンドを実行するcommand型でした。公式リファレンスでは、ハンドラーの種類としてcommandのほかにhttp・mcp_tool・prompt・agentの4つが用意されています(2026年10月14日確認)。種類が違っても、イベント・matcher・設定ファイルの構造は同じで、「何が動くか」と「結果をどう返すか」が変わります。
http型・mcp_tool型:外部サービスやMCPツールに判断を任せる
http型は、イベントの入力JSONをそのまま指定したURLにPOSTし、応答の本文をcommand型の標準出力と同じ形式のJSONとして読みます。社内の検証サービスに毎回のツール呼び出しを送って判断させる、といった使い方です。ヘッダーに環境変数の値を入れるときは、allowedEnvVarsに名前を列挙した変数だけが展開されます。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/pre-tool-use",
"timeout": 30,
"headers": { "Authorization": "Bearer $MY_TOKEN" },
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
}
mcp_tool型は、設定済みのMCPサーバーのツールをフックとして呼びます。inputの文字列には${tool_input.file_path}のように入力JSONの値を埋め込めます。ツールが返した文章は、command型の標準出力と同じ規則で読まれます。次は、WriteまたはEditのたびに my_server の security_scan ツールへ編集したファイルのパスを渡す例です(公式リファレンスの例)。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "mcp_tool",
"server": "my_server",
"tool": "security_scan",
"input": { "file_path": "${tool_input.file_path}" }
}
]
}
]
}
}
prompt型:Claudeのモデルに「進めてよいか」を判断させる
スクリプトの決まったルールでは判定しにくい、「すべての作業が終わったか」のような人間の判断に近いことを確かめたいときは、"type": "prompt"のフックを使います。コマンドを実行する代わりに、フックの入力データとあなたが書いた指示がClaudeのモデルに渡され、モデルは "ok": true(進める)か "ok": false(止める)をJSONで返します。次はStopイベントの例で、"ok": falseと返ると、Claudeはreasonの内容を次の指示として受け取り、作業を続けます。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Check if all tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains to be done\"}."
}
]
}
]
}
}
agent型:ファイルやテスト結果を調べてから判断する(実験的)
フックの入力データだけでは判断できず、実際のファイルを調べたりコマンドを実行したりして確かめたいときは、"type": "agent"のフックです。サブエージェントが起動し、ファイルの読み取りや検索などのツールを使って確かめてから(最大50ターン)、同じ ok の形で結果を返します。次の例は、テストが通るまでClaudeが終了しないようにします。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
"timeout": 120
}
]
}
]
}
}
タイムアウトの既定値は、command・http・mcp_toolが10分、promptが30秒、agentが60秒です。agent型のフックは、公式ドキュメントで「実験的で、動作や設定が今後変わる可能性がある」とされています。本番で使うワークフローには、まずcommand型を検討してください。なお、prompt型とagent型が使えるのは、PreToolUse・PostToolUse・PostToolUseFailure・UserPromptSubmit・Stop・SubagentStop・TaskCreated・TaskCompleted など、判断を返す意味があるイベントに限られます。SessionStartのように止められないイベントでは、command型とmcp_tool型だけが使えます。
5種類のハンドラーの比較
5種類のハンドラーを、何が動くか・結果の返し方・既定のタイムアウト・向く用途で比べると次のようになります(公式リファレンス 2026年10月14日確認)。
| 種類 | 何が動くか | 結果の返し方 | 既定の上限 | 向く用途 |
|---|---|---|---|---|
command | シェルコマンド・スクリプト | 終了コードと、標準出力のJSON | 600秒 | ほとんどの用途。依存が少なく、見本のJSONで単体で試せる |
http | 指定URLへのPOST | 2xx応答の本文のJSON | 600秒 | 社内サービスでの一括判定・記録 |
mcp_tool | MCPサーバーのツール | ツールの文章出力 | 600秒 | すでにMCPで持っている検査・通知の流用 |
prompt | Claudeのモデル(1往復) | {"ok": true/false, "reason"} | 30秒 | 「作業は全部終わったか」など判断を要する確認 |
agent | サブエージェント(ツール使用可) | {"ok": true/false, "reason"} | 60秒 | テスト結果や実ファイルを調べてからの判断(実験的) |
本記事の設定例がcommand型だけなのは、シェルスクリプトで完結し、見本のJSONを流して登録前に単体で試せるからです。外部サービスやMCPサーバーと密に連携する構成なら、http型・mcp_tool型を検討してください。
チームで使うとき:リポジトリで共有する・組織で固定する・拒否メッセージの書き方
ここまでは1人で使う前提でした。複数人の開発で使うときは、「誰の設定として置くか」と「止めたときのメッセージ」を決めておくと運用が楽になります(公式ドキュメント 2026年10月19日確認)。
プロジェクトの設定に置いて、リポジトリで共有する
プロジェクトの .claude/settings.json に書いたフックは、リポジトリにコミットしてチーム全員で共有できます。一方、個人だけで使いたいフックは .claude/settings.local.json に書きます。こちらはClaude Codeが設定を保存する際にgitignore対象にするため、コミットされません。フックから呼ぶスクリプトも .claude/hooks/ に入れて一緒にコミットし、設定には "$CLAUDE_PROJECT_DIR"/.claude/hooks/... のようにプロジェクト基準のパスで書けば、各自のクローン先が違っても同じ設定で動きます。ただし、新しくクローンした人は、最初の起動でフォルダを信頼する確認に同意するまでフックは動きません(「セキュリティ上の注意」の節)。
組織で固定する:管理設定とallowManagedHooksOnly
組織全体に必ず適用したい安全装置は、管理者が配る管理ポリシー設定に置きます。公式ドキュメントによると、フックは設定の階層をまたいで置き換えられずに統合されるため、ユーザー・プロジェクト・ローカルの設定は、管理設定のフックを消さずに自分のフックを足すだけです。また disableAllHooks も、管理設定の外からは管理フックを無効にできません。さらに管理設定で allowManagedHooksOnly を使うと、実行できるフックを管理設定のものに限れます。この場合、ユーザー・プロジェクト・ローカル・プラグインのフックはブロックされます(管理設定で強制的に有効にしたプラグインのフックは除きます)。
複数のリポジトリに同じフックを配る:プラグインにまとめる
リポジトリが多いと、同じ .claude/settings.json を各リポジトリにコピーして回ることになります。その場合は、フックとスクリプトをプラグインにまとめて配る方法があります。プラグインの中の hooks/hooks.json にフックを書くと、そのプラグインを有効にしている間だけ、ユーザーやプロジェクトのフックに足されて動きます。スクリプトの場所は ${CLAUDE_PLUGIN_ROOT} で書きます。
{
"description": "保存後の自動整形",
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
"timeout": 30
}
]
}
]
}
}
作ったプラグインは、社内用のプラグインマーケットプレイスに置くと、各自が /plugin から入れられます。組織で必ず使わせたいものは、前の管理設定で有効にしておきます(公式ドキュメント 2026年10月28日確認)。
拒否メッセージには、代わりの手段も書く
終了コード2で止めるとき、標準エラー出力に書いた理由はそのままClaudeに伝わります。本記事の実画面でも、Claudeはその文を読んで「フックにブロックされた」と説明していました。そこで、「なぜ止めたか」に加えて「代わりに何をしてほしいか」(たとえば、本番データはダンプ用のスクリプト経由で扱う、など)を1行添えておくと、Claudeが別の抜け道を探さず、決めた手順に切り替えやすくなります。チームのルールを伝える文面としても使えます。
フックで決めた環境変数を、Claudeのシェルへ引き継ぐ(CLAUDE_ENV_FILE)
フックのスクリプトで export した環境変数は、フックのプロセスの中だけのもので、あとでClaudeが実行するBashコマンドには引き継がれません。引き継ぎたいときは、SessionStartフックで CLAUDE_ENV_FILE が指すファイルに export の行を追記します(公式ドキュメント 2026年10月19日確認)。セッション中のBashコマンドは、実行のたびにこのファイルの内容を先に読み込みます。ほかのフックが書いた分を消さないよう、>> で追記します。
#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
fi
exit 0
なお、再開(--continue / --resume)したときは、UserPromptSubmitなどが過去のターンに足した文脈は再実行されず保存されたテキストが再生されるため、日付やコミットのIDのような値は古いままになります。最新の値が必要な内容は、SessionStartで出し直す設計にします。
コードを書かずに始めたいとき
公式ガイドによると、実現したいことを文章で説明して、Claudeにフックを書いてもらうこともできます。書いてもらったあとは、/hooks で登録されたイベントとmatcherを確認し、先に挙げた「見本のJSONを流して終了コードを確かめる」方法で、登録前に動きを試してください。
設定時の注意点とよくある落とし穴
コマンドのパスは絶対パスで指定する
Hooksコマンドはシェルのインタラクティブセッションとは異なる環境で実行されます。PATHの解決が期待どおりにならないケースがあるため、npxやnodeなども含め、絶対パスまたはbash -c '...'経由での実行が安定します。特にnvmやpyenvで管理しているランタイムは注意が必要です。
無限ループに注意する
フックのスクリプトが自分でファイルを書き換えても、それはClaudeのツール呼び出しではないため、PostToolUseが再び発火することはありません。ループに注意が必要なのはStopフックです。Stopフックが終了コード2で「まだ終わるな」と返し続けると、Claudeは作業を続け、再びStopが発火します。入力のJSONにあるstop_hook_activeがtrueのときは何もせず終了する、という判定を必ず入れてください。Claude Code側でも、ツール呼び出しを挟まずに8回連続でブロックされると、Stopフックを無視して終了する仕組みになっています。
タイムアウトを意識した設計
Hooksコマンドは、既定では完了するまでClaudeの処理を待たせます。実行時間が長いスクリプト(テストスイート全体の実行など)をPostToolUseに入れると、全体の応答が著しく遅くなります。command型の既定のタイムアウトは600秒(UserPromptSubmitは30秒)で、フックごとにtimeout(秒)で変更できます。タイムアウトしたフックは、既定では判定を返さずに捨てられ、ツール呼び出しはそのまま進みます。重い処理は、フックの設定に"async": trueを付けてバックグラウンドで実行するか、Stopイベントへの移動を検討してください。
matcherの正規表現の確認
matcherは、英数字と_・-・|などだけで書いた場合は完全一致として扱われます("Bash"はBashツールだけ、"Edit|Write"はEditかWriteのどちらか)。それ以外の文字を含むと正規表現として扱われ、名前の一部に一致すれば発火します。たとえば"Edit.*"はEditにもNotebookEditにも一致するため、全体一致にしたいときは^Edit$のように書きます。大文字と小文字は区別されます。
MCPツールにmatcherを合わせる:mcp__サーバー名__ツール名
MCPサーバーのツールは mcp__<サーバー名>__<ツール名> という名前で呼ばれます(例:mcp__memory__create_entities)。あるサーバーの全ツールに合わせたいときは、サーバー名の後ろに .* を付けます。mcp__memory とだけ書くと英数字と _ だけの「完全一致」として扱われ、どのツールにも一致しません。
| matcherの書き方 | 一致するもの |
|---|---|
mcp__memory__.* | memoryサーバーの全ツール |
mcp__.*__write.* | どのサーバーでも、名前が write で始まるツール |
Edit|Write | EditまたはWrite(完全一致の一覧。区切りは | か ,) |
^Edit$ | 正規表現で、Editだけ(NotebookEditを含めない) |
プラグインに含まれるMCPサーバーのツールは、サーバー名の部分にプラグイン名が入った mcp__plugin_<プラグイン名>_<サーバー名>__<ツール名> という形になるため、サーバーの素の名前で書いたmatcherは発火しません(公式ドキュメント 2026年10月27日確認)。
さらに絞りたいときは、前の「settings.jsonの基本スキーマ」で触れた if を使います。たとえば "if": "Bash(git *)" は、npm test && git push のような複数のコマンドをつないだ入力でも、その中に git push があれば発火します。$() やバッククォートの中のコマンドも調べられます。ただし、絞り込みは「できる範囲で行う」ものです。判定できない入力ではフックがそのまま動き、1つのハンドラーに書けるルールも1つだけなので、条件が複数あるときはハンドラーを分けます。絶対に守りたい許可・拒否は、フックの絞り込みでなくpermissionsのルールで決めます。
スクリプトに実行権限を付与する
外部スクリプトファイルを参照する場合、chmod +x ~/.claude/hooks/your_script.shのように実行権限を付与しておく必要があります。公式のトラブルシューティングにも、スクリプトがまったく動かないときは chmod +x で実行できるようにする、と載っています。
実行権限がない状態でスクリプトを動かすと、下の画面のように「許可がありません」となり、終了コードは126になりました。終了コード0でも2でもないため、フックとしては「ブロックしないエラー」の扱いで、処理は止まらず、本来のチェックも働きません。chmod +x を付けたあとは、同じスクリプトが期待どおりに終了コード2でブロックしました。

chmod +x のあと(終了コード2でブロック)の実行結果(2026年10月12日に実際に実行して撮影)Hooksを安定して使うための3つの心得
- 重くしない・止めない:ツール実行のたびに走るため、処理が重いと作業全体が遅くなります。補助的な処理は
"async": trueにするなど、本筋を止めない設計にします。 - 静かな失敗を確かめる:Hooksは裏で動くため、エラーに気づきにくいものです。期待した処理が本当に走っているかを、見本のJSONを流す方法や
claude --debugで確かめます。ガードレール用のフックには"onFailure": "block"を付けておくと、スクリプトが壊れたときに素通りせず止まります。 - 最初は一点に絞る:あれもこれもHooksに載せると全体像が追えなくなります。効果の大きい一つから始め、必要になったら足します。
セキュリティ上の注意:フックは自分の権限でコマンドを実行する
公式ドキュメントは、コマンド型のフックは自分のユーザー権限のままシェルコマンドを実行すると警告しています。そのアカウントが触れるファイルは、変更・削除・読み取りのどれでもできてしまうため、フックのコマンドは、設定に追加する前に内容を確認し、試してから使います(公式ドキュメント 2026年10月14日確認)。公式が挙げている心がけは、次の5つです。
- 入力を検証し、そのまま信用しない:標準入力のJSONの値も、中身を確かめてから使います
- シェル変数は必ず引用符で囲む:
$VARではなく"$VAR"と書きます - パスの「..」を弾く:ファイルパスに
..が含まれていないか確認し、想定外のフォルダへの移動を防ぎます - スクリプトは絶対パスで指定する:本記事の例のように
"$CLAUDE_PROJECT_DIR"/.claude/hooks/...と書きます - 機密ファイルを避ける:
.env・.git/・鍵ファイルなどは、フックの処理対象にしません(上の「保護対象ファイルへの編集をブロックする」の例が使えます)
もう一つ、チームで使うときの注意があります。設定ファイルのフックは、対話的に使っているときは、そのフォルダの「信頼するか」の確認(ワークスペースの信頼)に同意するまで実行されません。一方、claude -p のような非対話の実行では確認の画面が出ず、そのフォルダを信頼済みとして扱うため、リポジトリに入っている .claude/settings.json のフックがそのまま動きます。自分で書いていないリポジトリに対して非対話で実行するときは、先に .claude/ の設定を確認してください。また、ifフィールドでの絞り込みはあくまで「できる範囲で行う」ものなので、絶対に許可・拒否したい条件は、フックではなくpermissionsのルールで決めます(公式ドキュメントの記述)。
Hooksのデバッグ方法
Hooksが意図どおりに動かない場合のデバッグ手順です。手順2の動かし方は、下の画面と「危険なBashコマンドのブロック」の章の動画で示しています。手順4の --debug は、api,hooks のようにカテゴリを絞って指定できます。

echo でパイプして my-hook.sh に渡すと、スクリプトが受け取ったコマンドが表示され、echo $? で終了コード0を確認できます(2026年10月12日に実際に実行して撮影)- /hooks で登録を確認:
/hooksを実行し、フックが正しいイベントの下に表示されているかを確認します。設定ファイルの編集は通常自動で読み込まれます - スクリプト単体の動作確認:見本のJSONを標準入力に流してスクリプトを直接実行し、終了コードと出力を確認します。例:
echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh; echo $? - 記録を見る:
Ctrl+Oで記録(transcript)の表示を開くと、ブロックしたときのメッセージや「hook error」の通知を確認できます。成功したフックは何も表示されません - デバッグログ:
claude --debugで起動する(またはclaude --debug-file /tmp/claude.logでファイルに書き出す)と、フックの終了コード・標準出力・標準エラー出力まで確認できます。起動後に気づいたときは、セッションの中で/debugを実行するとログを有効にできます - matcher のテスト:matcherを
"*"に変えて全ツールにマッチさせ、発火しているかを確認した後、絞り込みます。matcherは大文字と小文字を区別します - 「command not found」「jq: command not found」と出るとき:スクリプトのパスには絶対パスか
${CLAUDE_PROJECT_DIR}を使います。jqが入っていない環境では、jqをインストールするか、PythonやNode.jsでJSONを読む形に変えます(公式ドキュメント 2026年10月14日確認) - JSONを返したのに効かないとき:標準出力が「JSONとして読めない」または「形式が合わない」場合は、終了コード0でもエラーとして扱われます。文字列をつなげて作らず
jqで組み立て、hookSpecificOutputの入れ子の位置が正しいかを確認します - フックを一時的に全部止める:設定ファイルに
"disableAllHooks": trueを書くか、起動時にclaude --settings '{"disableAllHooks": true}'を付けます。不具合の原因がフックかどうかの切り分けに使えます - Stopフックで止まらなくなったとき:Stopフックがツール呼び出しなしに8回連続でブロックすると、Claude Codeが打ち切ります。入力JSONの
stop_hook_activeがtrueなら何もせず終了する判定を入れてください(詳しくは「無限ループに注意する」の項)。上限は環境変数CLAUDE_CODE_STOP_HOOK_BLOCK_CAPで変えられます
よくある質問
Q. Claude Code Hooksとは何ですか?
A. Claude Codeのエージェント実行サイクルの特定タイミングに対して、任意のシェルコマンドやスクリプトを自動実行させる仕組みです。自動フォーマット、危険なコマンドのブロック、ログの記録、完了通知などに使えます。
Q. Hooksにはどんなライフサイクルイベントがありますか?
A. 公式ドキュメント(2026年10月14日確認)では33種類が定義されています。よく使うのはPreToolUse(ツール実行の直前)、PostToolUse(ツール実行の直後)、Notification(通知送信時)、Stop(Claudeが応答を終了するとき)の4つで、ほかにSessionStart・SessionEnd・UserPromptSubmit・PermissionRequest・SubagentStop・PreCompactなどがあります。
Q. Hooksの設定はどこに書きますか?
A. settings.jsonの”hooks”キー配下に記述します。適用範囲によってユーザーグローバル(~/.claude/settings.json)、プロジェクトローカル({project}/.claude/settings.json)、プロジェクトローカル個人用(settings.local.json)の3箇所があります。
Q. フックのスクリプトは、編集されたファイルや実行されるコマンドをどう受け取りますか?
A. 環境変数ではなく、標準入力(stdin)にJSONで渡されます。ファイルのパスは tool_input.file_path、Bashのコマンドは tool_input.command、ツール名は tool_name、セッションIDは session_id に入っているため、jq などで取り出します。環境変数として使えるのは、プロジェクトのディレクトリを表す CLAUDE_PROJECT_DIR などです。
Q. 終了コードによって動作はどう変わりますか?
A. 終了コード0は「異議なし」で処理を続行します。2はブロックで、stderrの内容が理由としてClaudeに伝わります。ブロックできるのはPreToolUse・UserPromptSubmit・Stop・SubagentStopなど「まだ起きていない動作」を表すイベントだけで、PostToolUseでは止められません(stderrはClaudeに表示されます)。それ以外の終了コードはブロックしないエラーとして扱われ、処理は続行します。
Q. matcherとは何ですか?
A. どのツール名に反応するかを指定するフィールドです。”Bash”や”Edit|Write”のように英数字と|だけで書くと完全一致、それ以外の文字を含むと正規表現として扱われます。ifフィールドを使うと、”Bash(git *)”のように特定のコマンドパターンだけに絞ることもできます。
Q. 複数のフックを登録すると、どの順番で実行されますか?
A. 同じイベントにマッチした複数のフックは並列に実行され、すべてが最後まで実行されます。1つがブロックを返してもほかのフックは止まりません。PreToolUseの判定は、いちばん厳しい結果(deny)が優先されます。
Q. command型以外にどんなハンドラー種別がありますか?
A. 公式仕様ではcommand(シェルコマンド実行)のほか、http(外部エンドポイントへのHTTP呼び出し)・mcp_tool(MCPツールの呼び出し)・prompt(Claudeへの追加指示挿入)・agent(サブエージェント起動)の合計5種類が用意されています。
Q. Hooksとpermissions設定はどう使い分けますか?
A. Hooksはスクリプトでコマンドの内容を見て動的にブロック判定でき、ブロック理由をClaudeに伝えられます。permissions(allow・ask・denyのルール)は、ツール名やコマンドのパターンを決め打ちで許可・拒否する仕組みです。条件によって判断を変えたい場合はHooks、決まったツールやコマンドを常に禁止したい場合はpermissionsが適しています。
Q. Hooksがうまく動かないときはどうデバッグしますか?
A. /hooks で登録を確認し、見本のJSONを標準入力に流してスクリプトを直接実行して終了コードを確かめます。Ctrl+O の記録表示でブロックのメッセージや hook error の通知を確認でき、claude –debug で起動すると終了コードや出力の詳細を確認できます。
Q. .envなどの特定のファイルへの編集を禁止できますか?
A. できます。PreToolUseにEdit|Writeのmatcherでフックを登録し、標準入力のJSONの tool_input.file_path が .env・package-lock.json・.git/ などの保護対象を含むときに、終了コード2で終了します。止めた理由は標準エラー出力に書くと、Claudeに伝わります。
Q. フックでツール呼び出しを拒否または許可するには、どう書きますか?
A. 終了コード0で終了し、標準出力にJSONを書きます。PreToolUseでは、hookSpecificOutput の中に permissionDecision(allow・deny・ask)と、denyのときの理由 permissionDecisionReason を入れます。denyにすると、ツール呼び出しが取りやめになり、理由がClaudeに伝わります。終了コード2でブロックする方法とJSONで返す方法は、1つのフックの中ではどちらか一方にします。
まとめ
Claude Code Hooksは、Claude Codeの実行サイクルにある33種類のイベント(よく使うのはPreToolUse・PostToolUse・Notification・Stop)に、シェルコマンドなどを差し込む仕組みです。フックは標準入力のJSONでイベントの情報を受け取り、終了コード(2でブロック)か標準出力のJSON(permissionDecisionなど)でClaude Codeに判断を返します。本記事で実際に動かしたとおり、PreToolUseのフックが終了コード2を返せば、権限の確認に進む前にコマンドが止まり、その理由がClaudeに伝わります。
導入は、効果の分かりやすい2つ(保存後の自動フォーマットと、危険なコマンドのブロック)から始め、必要に応じて監査ログ・通知・保護ファイルのガードを足していくのが定着しやすい進め方です。プロジェクトの.claude/settings.jsonにフックを書いてgitにコミットすれば、チーム全員の環境に同じルールが自動で適用されます。決まったツールやコマンドを常に禁止したいだけならpermissions、方針や背景を伝えたいだけならCLAUDE.mdと使い分け、スクリプトは見本のJSONを流して単体で試してから登録してください。
関連記事
- Claude Codeとは
- Claude CodeでSEOに取り組む(初心者向け)
- Claude Codeの使い方
- Claude Codeの料金と無料枠
- Claude Codeのインストール方法
- Claude Codeの始め方
- Claude Codeのコマンド一覧
- Claude Codeの権限設定(permissions)
- Claude Codeのサンドボックス
- Claude Codeのセキュリティ
- Claude Codeで自動化・定期実行する方法
Claude Code・AIエージェントの業務活用をご検討の方へ
クリスタルメソッドは、Claude Codeを日々の業務で使いながら、AIエージェント・社員AIの導入と開発効率化を支援しています。自社サイトの表示速度をAI社員(Claude Code)で12.89秒→2.03秒に短縮した実例も事例記事として公開しています。「自社の開発・業務にAIをどう組み込むか」といったご相談を承っています。
- 無料相談・お問い合わせ:ご相談はこちら
Read next
あわせて読みたい
-
Claude Code 自動化(automation)実装ガイド|Hooksのコード例と失敗しない自動化設計【2026】
Claude Codeは、ターミナルから複数ステップのタスクを自律的に完遂できるAIコーディングエージェントです。公式ドキュメント(headless.md)が定...
-
Claude Code 権限設定|allow/deny・最小権限【2026】
Claude Codeの権限設定は、エージェントにどのツールやコマンドの実行を許可/拒否するかを制御する、安全に使ううえで最初に押さえるべき仕組みです。本記事で...
-
Claude Code Skillsとは?公式スキル一覧・作り方・使い方をわかりやすく解説【2026年版】
Claude Code Skills(スキル)とは、Claude Codeに「この場面ではこう進めてほしい」という手順書(SKILL.md)を追加して、必要なと...
