Claude Codeのフック(Hooks)とは?設定方法と使い方・例を解説
この記事でわかること
- フックとは何か、CLAUDE.md・スキル・権限設定との違い
- 最初のフック(通知)を設定する手順と、設定ファイルの置き場所
- イベント・matcher・if・終了コードなど、フックのしくみ
- 自動整形・ファイルの保護・危険なコマンドの阻止など、よく使う例
- Windowsでの書き方(PowerShell・パスの区切り・jqの代わり)
- 安全に使うための注意と、フックが動かないときの確かめ方
目次
- Claude Codeのフックとは
- ひとことで言うと
- CLAUDE.mdとの違い
- スキル・サブエージェント・権限設定との使い分け
- フックでできること
- フックでできないこと
- 設定のしかた・置き場所
- 設定ファイルの形
- 最初のフック:Claudeが待っているときに通知する
- 設定ファイルの置き場所
- Claudeに書いてもらう
- 設定したフックを確かめる
- フックのしくみ
- イベント:動くタイミング
- matcher:どんなときに動かすか
- ifフィールド:引数まで見て絞る
- フックが受け取る情報
- フックの返し方:終了コード
- JSONで細かく返す
- Claudeに情報を足す
- 複数のフックが動くとき
- タイムアウト
- よく使う例
- 例1:編集のたびに自動整形する
- 例2:重要なファイルを書き換えさせない
- 例3:危険なコマンドを止める
- 例4:圧縮のあとも前提を思い出させる
- 例5:プロンプトに今の状況を足す
- 例6:実行したコマンドを記録する
- 例7:設定の変更を記録する
- 例8:終わる前にテストを確認する
- 例9:決まった許可だけを自動で承認する
- Windowsでの書き方
- どのシェルで動くか
- PowerShellでフックを書く
- 例:PowerShellで書いたファイル保護
- パスの区切りに注意する
- jqが入っていないとき
- .cmdや.batのコマンドを呼ぶとき
- Git Bashの設定ファイルが出力を邪魔する
- Windowsでよくあるつまずき
- フックの種類・非同期実行
- promptフック:判断をモデルに任せる
- agentフック:確かめてから判断する
- httpフック:外部のサービスに任せる
- mcp_toolフック:MCPサーバーの処理を呼ぶ
- バックグラウンドで動かす(async)
- スキルやサブエージェントに入れる
- 安全に使うための注意
- フックは自分の権限で動く
- 他人のリポジトリのフックに注意する
- 権限設定との関係
- フックが失敗したとき、動作を止めるか
- フックを無効にする・制限する
- 動かないときの確かめ方
- よくある質問
- まとめ
「テストを実行してからコミットして」と毎回頼んでいるのに、たまに忘れられる。「.envだけは絶対に書き換えてほしくない」。「作業が終わったら、ほかの作業をしていても気づきたい」。Claude Codeを使い込むほど、こうした悩みが出てきます。
これらを解決するのが、Claude Codeの「フック(Hooks)」です。フックは、Claude Codeの動きの決まったタイミングで、自分で用意したコマンドを自動的に実行させる機能です。Claudeに「お願い」するのではなく、条件が合えば毎回必ず動くところが、CLAUDE.mdなどの指示との大きな違いです。
この記事では、フックの考え方から、最初のフックを作る手順、イベントや条件の指定のしかた、よく使う例、Windowsでの書き方、安全に使うための注意までを順に解説します。内容は2026年10月時点の公式ドキュメントをもとにしています。機能は更新が続いているため、細かい仕様は公式ページもあわせて確認してください。
Claude Codeのフックとは

Claude Codeには、Claudeが作業を進める途中で、「プロンプトを受け取ったとき」「ファイルを編集する直前」「ファイルを編集した直後」「応答を終えたとき」といった節目があります。フックは、こうした節目に自分で決めた処理をはさみ込むための仕組みです。
ひとことで言うと
公式ドキュメントでは、フックを「ユーザーが定義したシェルコマンド、HTTPエンドポイント、MCPツールの呼び出し、LLMへのプロンプト、サブエージェントを、Claude Codeの動作の決まったタイミングで自動実行するもの」と説明しています。いちばんよく使うのは、シェルコマンド(パソコン上で動かすコマンド)です。
公式ガイドの言葉を借りると、フックは「LLMが実行するかどうかを選ぶのではなく、特定の処理が必ず行われる」という決まった動作を作るものです。ファイルを書き換えたら整形ツールを動かす、特定のファイルへの書き込みを止める、といった「毎回同じことをしたい」場面に向いています。
なお、フックは端末(ターミナル)だけの機能ではありません。公式ドキュメントでは、IDE拡張、デスクトップアプリ、クラウド上のセッションでも、同じイベントでフックが動くと説明されています。
CLAUDE.mdとの違い
「お願い」と「仕組み」の違いを整理します。CLAUDE.mdに「コミット前にテストを実行する」と書いておけば、Claudeはたいていその通りに動きます。ただし、守るかどうかはClaudeの判断に任されています。フックは、判断を挟まずに実行されます。
| CLAUDE.md | フック | |
|---|---|---|
| 性質 | Claudeへの指示・前提(読んで、従うかをClaudeが判断する) | 条件が合えば、必ず実行されるコマンドなど |
| 書くもの | 文章 | 設定(JSON)とスクリプト |
| 向いている内容 | プロジェクトの前提、コーディングの方針、用語 | 整形・通知・ファイルの保護・記録など、例外なく行いたい処理 |
| Claudeに見えるか | 毎回読み込まれて、Claudeの文脈に入る | 出力の多くは画面にも出ない。ブロックの理由や、追加した情報などだけがClaudeに渡る |
公式ガイドでも、毎回のセッションで同じ前提を渡したいだけなら、フックではなくCLAUDE.mdを使うよう案内されています。フックは、「前提を伝える」仕組みではなく、「動作そのものを決める」仕組みです。
スキル・サブエージェント・権限設定との使い分け
Claude Codeには、フックのほかにも動作をカスタマイズする仕組みがあります。迷ったときは、次の表で「何をしたいか」から選びます。
| やりたいこと | 向いている仕組み | 理由 |
|---|---|---|
| プロジェクトの前提や方針を毎回伝えたい | CLAUDE.md | 文章で書くだけでよく、スクリプトが要らない |
| 特定の作業の手順書を持たせたい | スキル | 必要なときだけ読み込まれる手順書。Claudeが判断して使う |
| 調査などを別の担当に任せたい | サブエージェント | 独立した文脈で作業し、結果の要約だけが戻る |
| 特定の操作を例外なく止めたい・必ず実行したい | フック | Claudeの判断に関係なく動く |
| ツールの許可・拒否の基本ルールを決めたい | 権限設定(permissions) | 公式は、確実な許可・拒否にはフックより権限設定を使うよう案内している |
最後の行は見落としやすい点です。公式ドキュメントでは、フックのifによる絞り込みは「ベストエフォート(できる範囲での絞り込み)」とされており、厳格に許可・拒否を決めたいなら、フックではなく権限の仕組みを使うように書かれています。フックは、その上で「権限設定では表現できない条件」や「実行の前後の処理」を足すものと考えると整理しやすくなります。
フックでできること
公式ガイドで紹介されている用途を、目的別にまとめます。
| やりたいこと | 使うイベント | 具体例 |
|---|---|---|
| 通知する | Notification | Claudeが許可待ちになったとき、デスクトップに通知を出す |
| コードを自動で整える | PostToolUse | ファイルを編集するたびに整形ツール(Prettierなど)を実行する |
| 特定のファイルを守る | PreToolUse | .envやpackage-lock.jsonの編集を止める |
| 危険なコマンドを止める | PreToolUse | rm -rfを含むコマンドを実行前に拒否する |
| 情報をClaudeに足す | SessionStart・UserPromptSubmit | 現在のブランチや「今週はデプロイ凍結」などをプロンプトに添える |
| 圧縮後に前提を思い出させる | SessionStart(compact) | 会話の圧縮で消えかねない規約を、圧縮のたびに再注入する |
| 操作を記録する | PostToolUse・ConfigChange | 実行したコマンドや、設定ファイルの変更をログに残す |
| 終わる前に確認させる | Stop | 全部のタスクが終わっているか、テストが通るかを確かめてから止まらせる |
| 許可の確認を省く | PermissionRequest | 決まった種類の許可ダイアログだけを自動で承認する |
フックでできないこと
フックは強力ですが、できないこともあります。公式ドキュメントに書かれている制約を、先に押さえておきましょう。
- スラッシュコマンドやツール呼び出しは起動できない:コマンド型のフックは、標準出力・標準エラー出力・終了コードでやりとりします。
/で始まるコマンドや、ツールの呼び出しを起こすことはできません - PostToolUseは取り消せない:ツールが実行された後に動くため、実行済みの操作を元に戻すことはできません。止めたいなら、実行前のPreToolUseで止めます
- Stopはいつでも動くわけではない:Claudeの応答が終わるたびに動きますが、ユーザーが中断したときは動きません。APIエラーで終わったときは、別のStopFailureというイベントが動きます
- 追加した順番に動くわけではない:同じイベントに合致するフックは並列で実行されます。順序に依存する処理は、1つのスクリプトにまとめます
「フック」と呼ばれる別の仕組みについて
公式ドキュメントには、プラグインが「JavaScriptの関数としてフックを登録する」仕組み(mod)も出てきますが、これは本記事で扱う設定ファイルのフックとは別の機能です。この記事で説明するのは、設定ファイルに書くコマンド型などのフックです。
設定のしかた・置き場所

フックは、settings.jsonという設定ファイルに書きます。専用のアプリや画面はありません。まず設定ファイルの形を理解し、いちばん簡単な「通知」のフックを実際に動かしてみましょう。
設定ファイルの形
フックの設定は、3段の入れ子になっています。公式ドキュメントでは、次の3つを選ぶ形だと説明されています。
- イベントを選ぶどのタイミングで動かすか。
PreToolUse(ツールの実行前)やStop(応答の終了時)などです - matcher(条件)を決めるそのイベントのうち、どんな場合だけ動かすか。「Bashツールのときだけ」のように絞ります
- ハンドラー(実行する処理)を書く動かすコマンドなどを指定します。1つの条件に、複数を並べられます
形にすると、次のようになります。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "実行したいコマンド"
}
]
}
]
}
}
この例では、PostToolUse(ツールが成功した後)というイベントの、EditかWrite(ファイルの編集・書き込み)のときだけ、commandに書いたコマンドを実行します。"type": "command"は「シェルコマンドを実行する」という意味です。
JSONの書き間違いに注意
設定ファイルはJSONという形式で、最後の項目の後ろにカンマを付けたり、コメントを書いたりするとエラーになります。公式ガイドでも、フックが一覧に出てこないときの確認項目として「末尾のカンマやコメントは使えない」と挙げられています。すでにhooksがある場合は、全体を置き換えるのではなく、イベント名を同じ階層に追加します。
最初のフック:Claudeが待っているときに通知する
公式ガイドの最初の例も、通知のフックです。Claudeが許可や入力を待っているときに通知が出るので、ターミナルを見張らなくても済みます。リスクが小さく、動いたかどうかも分かりやすいため、最初の1本に向いています。
- 設定ファイルを開くユーザー全体の設定ファイル
~/.claude/settings.jsonを開きます。なければ作ります。Windowsでは、ユーザーフォルダの中の.claudeフォルダにあるファイルです - Notificationフックを書く下のJSONを、自分のOSに合わせて追記します
- /hooksで確認するClaude Codeの入力欄に
/hooksと入力し、Notificationの下に追加したフックが出ていることを確かめます - 試すClaudeに許可が必要な作業(ファイルの編集など)を頼み、別のウィンドウに切り替えます。許可待ちになると通知が出ます
Macの場合の設定は次のとおりです(公式ガイドの例です)。
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}
Windows(PowerShell)の場合は、commandの中身が次のように変わります。通知の形は、画面の隅に出る通知ではなく、ダイアログボックス(小さな確認窓)になります。
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "powershell.exe -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code needs your attention', 'Claude Code')\""
}
]
}
]
}
}
Linuxの場合はnotify-send 'Claude Code' 'Claude Code needs your attention'を使います。
| OS | 使うコマンド | 通知が出ないときの確認 |
|---|---|---|
| Mac | osascript | 通知は標準の「スクリプトエディタ」経由で出る。そのアプリに通知の許可がないと、エラーも出ずに失敗する。システム設定の「通知」で許可する |
| Linux | notify-send | デスクトップの通知デーモンが必要。サーバーやSSH接続、多くのコンテナでは使えない。入っていなければ、ディストリビューションに合ったパッケージを入れる |
| Windows | powershell.exe | ダイアログが、ターミナルの後ろに開くことがある。PowerShellで同じコマンドを直接実行して確かめる。WSLで動かす場合は、powershell.exeがPATHに通っている必要がある |
"matcher": ""と空にしているため、あらゆる種類の通知で動きます。許可待ちのときだけにしたいならpermission_prompt、60秒ほど放置されたときだけならidle_promptを指定します。
| matcherの値 | 動くタイミング |
|---|---|
permission_prompt | ツールの使用許可などを求められ、その確認が約6秒待たれているとき |
idle_prompt | Claudeの応答が終わって約60秒たち、その間に入力がないとき |
auth_success | 認証が完了したとき |
elicitation_dialog | MCPサーバーが入力フォームを開き、約6秒入力がないとき |
ターミナルの画面では、許可待ちの通知は「しばらく入力していないとき」だけ出ます。許可のダイアログが出た瞬間に動かしたい場合は、後述するPermissionRequestを使います。
設定ファイルの置き場所
フックをどの設定ファイルに書くかで、効く範囲と、チームで共有できるかが変わります。
| 置き場所 | 効く範囲 | 共有 |
|---|---|---|
~/.claude/settings.json | 自分のすべてのプロジェクト | できない(自分のパソコンだけ) |
.claude/settings.json | そのプロジェクトだけ | できる(リポジトリにコミットして共有) |
.claude/settings.local.json | そのプロジェクトだけ | できない(Claude Codeが保存する際はgitの管理対象外にされる) |
| 管理者のポリシー設定 | 組織全体 | できる(管理者が制御) |
プラグインの hooks/hooks.json | プラグインを有効にしている間 | できる(プラグインに同梱) |
| スキルのフロントマター | そのスキルを呼び出した後、セッションの間 | できる(スキルのファイルに書く) |
| サブエージェントのフロントマター | そのサブエージェントが動いている間 | できる(サブエージェントのファイルに書く) |
使い分けの目安は、次のとおりです。通知のように自分の好みの問題は~/.claude/settings.json、整形やファイルの保護のようにチームで共通のルールは.claude/settings.jsonに書いてコミットします。
設定ファイルを直接書き換えた場合は、通常は自動で反映されます。数秒たっても/hooksに出てこないときは、セッションを開き直します。
クラウド上のセッションでは、手元のユーザー設定は使われない
公式ドキュメントによると、クラウド上のセッションは、パソコンの~/.claude/settings.jsonを読みません。クラウドでも同じフックを使いたい場合は、プロジェクトの.claude/settings.jsonに書いてリポジトリに入れておきます。
Claudeに書いてもらう
JSONやスクリプトを自分で書かなくても、Claude Codeに頼んで作ってもらうことができます。公式ガイドにも「やりたいことを説明すれば、Claudeがフックを書いてくれる」と書かれています。たとえば次のように頼みます。
ファイルを編集するたびに、Prettierで自動整形するフックを、このプロジェクトの.claude/settings.jsonに追加してください。
Claudeが書いた設定は、そのまま信用せずに/hooksで内容を確かめ、実際に動かして試してから使い始めます。フックはあなたのユーザー権限でコマンドを実行するため、中身を自分で理解してから有効にするのが基本です(詳しくは後述の「安全に使う」の節で説明します)。
設定したフックを確かめる
Claude Codeで/hooksと入力すると、設定されているフックを一覧で見られます。この画面は読み取り専用で、フックの追加や変更はできません。主な用途は次の3つです。
- 各フックがどこから来ているか(ユーザー設定・プロジェクト設定・ローカル設定・プラグインなど)の確認
- 選んだフックの実行内容と、定義されているファイルの確認
- 末尾の「All events」から、設定のないものも含めた全イベントの確認
一覧に出てくれば、設定ファイルの読み込みと書式は正しいということです。実際に動いたかどうかは、フックの効果(ファイルが整形された、通知が出た、など)で確かめます。成功したフックは、通常は会話の画面に何も表示しません。
フックのしくみ

ここからは、設定を自分で書いたり、動かないときに原因を探したりするために必要な、フックの内部の考え方を説明します。「いつ動くか(イベント)」「どれを動かすか(matcher・if)」「何を受け取るか(入力)」「どう返すか(終了コードと出力)」の4つが押さえどころです。
イベント:動くタイミング
公式の一覧には、2026年10月時点で30を超えるイベントがあります。動く頻度で見ると、次の3つに分かれます。
- セッションごと:
SessionStart(開始・再開)、SessionEnd(終了) - ターン(1回のやりとり)ごと:
UserPromptSubmit(プロンプトの受け取り)、Stop(応答の終了)、StopFailure(APIエラーでの終了) - ツールの呼び出しごと:
PreToolUse(実行前)、PostToolUse(実行後)
まず、よく使うイベントを表にまとめます。「ブロックできるか」は、終了コード2を返したときに、その動作を止められるかどうかです。
| イベント | 動くタイミング | ブロックできるか | 主な使い道 |
|---|---|---|---|
SessionStart | セッションの開始・再開時 | できない | 前提や現在の状況をClaudeに渡す、環境変数の設定 |
UserPromptSubmit | プロンプトを送信した直後、Claudeが処理する前 | できる | プロンプトの検査、情報の追加 |
PreToolUse | ツールを実行する直前 | できる | 危険な操作・保護したいファイルの阻止、入力の書き換え |
PermissionRequest | ツールの使用許可を求める直前 | 終了コードではできない(JSONで拒否する) | 許可ダイアログの自動承認・自動拒否 |
PostToolUse | ツールが成功した直後 | できない(実行済み) | 自動整形、ログ、結果への注釈 |
PostToolUseFailure | ツールが失敗した直後 | できない | 失敗の記録、Claudeへの補足 |
Stop | Claudeが応答を終えたとき | できる(続きの作業をさせる) | 完了条件の確認、テストの実行 |
Notification | Claude Codeが通知を出すとき | できない | デスクトップ通知、外部サービスへの転送 |
PreCompact | 会話の圧縮の直前 | できる | 圧縮を止める |
SessionEnd | セッションが終わるとき | できない | 後片付け、記録 |
そのほかのイベントは次のとおりです。必要になったときに引けるよう、一覧で載せておきます。
| イベント | 動くタイミング | ブロックできるか |
|---|---|---|
Setup | --init-onlyでの起動、または-pモードでの--init・--maintenance。CIなどでの1回限りの準備用 | できない |
UserPromptExpansion | 入力したコマンドがプロンプトに展開されるとき | できる |
PermissionDenied | 自動モードでツールの呼び出しが拒否されたとき | できない |
PostToolBatch | 並列のツール呼び出しがすべて終わった後、次のモデル呼び出しの前 | できる |
MessageDisplay | Claudeのメッセージの文章が表示されるとき | できない(表示だけ) |
SubagentStart / SubagentStop | サブエージェントの起動時・終了時 | Startはできない、Stopはできる |
TaskCreated / TaskCompleted | タスクの作成時・完了扱いにするとき | どちらもできる |
StopFailure | APIエラーでターンが終わったとき | できない |
TeammateIdle | エージェントチームのメンバーが待機に入るとき | できる |
InstructionsLoaded | CLAUDE.mdや.claude/rules/のファイルが読み込まれたとき | できない |
ConfigChange | セッション中に設定ファイルが変わったとき | できる(管理者設定を除く) |
CwdChanged | 作業ディレクトリが変わったとき | できない |
DirectoryAdded | /add-dirなどで作業ディレクトリが追加されたとき | できない |
FileChanged | 監視しているファイルがディスク上で変わったとき | できない |
WorktreeCreate / WorktreeRemove | ワークツリーの作成時・削除時 | どちらも、失敗させることはできる |
PostCompact | 会話の圧縮が終わった後 | できない |
PreModelSwitch / PostModelSwitch | モデルの切り替えの前後 | Preはできる、Postはできない |
Elicitation / ElicitationResult | MCPサーバーが入力を求めたとき、ユーザーが応答した後 | どちらもできる |
新しいイベントは追加されていくため、公式の「Hooks reference」の一覧を確認するのが確実です。
matcher:どんなときに動かすか
matcherを書かない(空にする)と、そのイベントが起きるたびにフックが動きます。matcherを書くと、「特定のツールのときだけ」「特定の始まり方のときだけ」のように絞れます。
何に対して照合されるかは、イベントごとに決まっています。
| イベント | matcherが絞り込む対象 | 値の例 |
|---|---|---|
| PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest、PermissionDenied | ツールの名前 | Bash、Edit|Write、mcp__.* |
| SessionStart | セッションの始まり方 | startup(新規)、resume(再開)、clear、compact(圧縮後)、fork |
| SessionEnd | 終わった理由 | clear、resume、logout、prompt_input_exit、other |
| Notification | 通知の種類 | permission_prompt、idle_prompt |
| SubagentStart、SubagentStop | サブエージェントの種類 | Explore、Plan、自作の名前 |
| PreCompact、PostCompact | 圧縮のきっかけ | manual、auto |
| ConfigChange | 設定ファイルの種類 | user_settings、project_settings、local_settings、policy_settings、skills |
| FileChanged | 監視するファイル名 | .envrc|.env |
| UserPromptSubmit、Stop、PostToolBatch など | matcherには対応していない | (常にすべての発生で動く) |
matcherの値の書き方には、次のルールがあります。
| matcherの値 | どう評価されるか | 例 |
|---|---|---|
"*"、空文字、または省略 | すべてに一致 | そのイベントのすべての発生で動く |
英数字・_・-・空白・,・|だけ | 完全一致の文字列、または|か,で区切った複数の候補 | BashはBashツールだけ。Edit|WriteとEdit, Writeは、どちらも2つのツールのどちらか |
| それ以外の文字を含む | JavaScriptの正規表現(部分一致) | ^Notebookは名前がNotebookで始まるツール。mcp__memory__.*はmemoryサーバーの全ツール |
注意点が2つあります。まず、matcherは大文字・小文字を区別します。bashと書いてもBashツールには一致しません。次に、正規表現になる場合は部分一致のため、Edit.*はEditとNotebookEditの両方に一致します。完全一致にしたいときは^Edit$のように書きます。
MCPサーバーのツールは、mcp__サーバー名__ツール名という名前になります。サーバーの全ツールを対象にするには、mcp__memory__.*のように.*を付けます。mcp__memoryだけでは、完全一致の文字列として扱われ、どのツールにも一致しません。
matcherに対応していないイベントでは、無視される
UserPromptSubmitやStopのようにmatcherに対応していないイベントにmatcherを書いても、エラーにはならず、黙って無視されます。意図した絞り込みが効いていないことに気づきにくいので、イベントの対応表を確認してください。
ifフィールド:引数まで見て絞る
matcherはツールの名前だけを見ます。「Bashツールのうち、gitで始まるコマンドのときだけ」のように、ツールの引数まで見て絞りたいときは、各フックにifを書きます。ifには、権限設定と同じ書き方(Bash(git *)、Edit(*.ts)など)を使います。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(git *)",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-git-policy.sh"
}
]
}
]
}
}
こうしておくと、Bashツールがgit系のコマンドを実行するときだけスクリプトが起動します。ほかのコマンドでは、スクリプトのプロセス自体が起動しないため、無駄な待ち時間も生まれません。
Bashのコマンドに対するifの判定は、次のようになります(公式の表より)。
ifの書き方 | Claudeが実行するコマンド | 動くか | 理由 |
|---|---|---|---|
Bash(git *) | git push | 動く | コマンド名が一致する |
Bash(git *) | npm test && git push | 動く | つながったコマンドを個別に調べ、git pushが一致する |
Bash(git *) | FOO=bar git push | 動く | 先頭の環境変数の代入は無視して照合する |
Bash(git *) | echo $(git log) | 動く | $()やバッククォートの中も調べ、git logが一致する |
Bash(git *) | echo $(date) | 動かない | どのコマンドもgit *に一致しない |
Bash(git push *) | echo $(date) | 動く | コマンド名以上を指定したパターンは、$()などがあると安全側に倒して動かす |
最後の行のように、Claude Codeが「どのコマンドが実行されるか判断できない」場合は、パターンに関係なくフックを動かします。ifはあくまで「ベストエフォートの絞り込み」です。確実な拒否・許可は、権限設定で行います。
そのほか、ifには次の制約があります。
- 書けるルールは1つだけです。
&&や||、リスト形式はありません。複数の条件がある場合は、フックを条件ごとに分けて書きます - 使えるのは、ツール系のイベント(PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest、PermissionDenied)だけです。それ以外のイベントに書くと、そのフックは動きません
- ファイルツールの
Edit(src/**)は、作業ディレクトリ直下のsrcだけに一致します。どの深さのsrcにも一致させたいときはEdit(**/src/**)と書きます
フックが受け取る情報
コマンド型のフックは、起動時に、イベントの情報をJSONで標準入力から受け取ります。たとえばClaudeがnpm testを実行しようとしたとき、PreToolUseのフックには次のような情報が渡されます。
{
"session_id": "abc123",
"cwd": "/Users/sarah/myproject",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}
どのイベントにも共通して入っている主な項目と、イベントごとに増える項目は次のとおりです。
| 項目 | 内容 | 入っているイベント |
|---|---|---|
session_id | セッションのID | すべて |
cwd | フックが呼ばれたときの作業ディレクトリ | すべて |
hook_event_name | 起きたイベントの名前 | すべて |
transcript_path | 会話の記録ファイルのパス | すべて |
permission_mode | 現在の権限モード(default、plan、acceptEdits、auto、dontAsk、bypassPermissions) | 多くのイベント |
tool_name、tool_input | 使うツールの名前と、その引数 | PreToolUse、PostToolUseなど |
tool_response | ツールが返した結果 | PostToolUse |
prompt | 送信されたプロンプトの文 | UserPromptSubmit |
last_assistant_message、stop_hook_active | Claudeの最後の応答の文、Stopフックによって続行中かどうか | Stop |
source | startup・resume・clear・compact・fork | SessionStart |
message、notification_type | 通知の文と種類 | Notification |
ファイルを扱うツール(Write・Edit・Read)のtool_input.file_pathは、常に絶対パスで渡されます。~や相対パスはClaude Code側で展開されてから渡されるため、「~と書いて検査をすり抜ける」ことはできません。
Bashのスクリプトからこれらを取り出すには、jqというJSON整形コマンドをよく使います。jq -r '.tool_input.command'と書けば、実行しようとしているコマンドを取り出せます。jqがない環境では、PythonやNode.jsで読むこともできます(Windowsの場合は後述します)。
フックの返し方:終了コード
フックが処理を終えるときの終了コード(プログラムが終わるときに返す数字)で、Claude Codeに次の動作を伝えます。
| 終了コード | 意味 | 起きること |
|---|---|---|
0 | 成功(異議なし) | 動作はそのまま進む。PreToolUseで0を返しても「許可した」ことにはならず、通常の許可の流れが続く。UserPromptSubmit・SessionStartなどでは、標準出力の文字がClaudeの文脈に追加される |
2 | ブロック | ブロックできるイベントでは、その動作を止める。標準エラー出力に書いた文が、止めた理由になる |
| それ以外(1など) | 非ブロックのエラー | 動作はそのまま進む。「hook error」という通知が会話の記録に出る |
終了コード1ではブロックされない
Unixの慣習では、1は「失敗」を表す数字です。しかしClaude Codeでは、1は非ブロックのエラーとして扱われ、動作は止まりません。公式ドキュメントも「ポリシーを強制するフックにはexit 2を使う」と警告しています。スクリプトの途中でエラーが起きて1が返ると、「止めるつもりが素通り」になるので注意してください。
ブロックするスクリプトの形は、次のようになります。
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q "drop table"; then
echo "Blocked: dropping tables is not allowed" >&2 # 標準エラー出力 = Claudeへのフィードバック
exit 2 # 2 = ブロック
fi
exit 0 # 0 = 判断なし。通常の許可の流れに進む
標準エラー出力に書いた文は、Claudeに「ツールが拒否された理由」として返ります。Claudeはその文を読んで、別のやり方を探します。理由は具体的に書くほど、Claudeが次の行動を選びやすくなります。
終了コード2の効果はイベントによって違います。止められないイベントでは、2を返しても動作は止まりません。
| イベント | 終了コード2を返したときの動き |
|---|---|
| PreToolUse | ツールの呼び出しを止める。理由はClaudeに渡る |
| UserPromptSubmit | プロンプトを止める。Claudeには届かない。理由はユーザーに表示される |
| Stop | Claudeが止まるのを防ぎ、会話を続けさせる。理由はClaudeへの指示になる |
| PostToolUse | 止められない(ツールは実行済み)。標準エラー出力がClaudeに渡る |
| PermissionRequest | 効果がない。拒否するにはJSONのdecisionを使う |
| Notification、SessionEnd | 止められない。標準エラー出力は無視、またはユーザーにのみ表示される |
| SessionStart | 止められない。標準エラー出力はユーザーにのみ表示される |
| ConfigChange | 設定の変更を止める(管理者設定を除く) |
JSONで細かく返す
終了コードだけでは、「止める」か「何もしない」かしか表せません。もう少し細かく制御したいときは、終了コード0のまま、標準出力にJSONを1つだけ出力します。公式ドキュメントは、1つのフックで「終了コードで伝える」か「JSONで伝える」かのどちらかに統一するよう案内しています。
たとえばPreToolUseでは、次のようにして拒否し、理由をClaudeに返せます。
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "grepではなくrgを使ってください"
}
}
PreToolUseのpermissionDecisionには、4つの値があります。
| 値 | 動き |
|---|---|
allow | 許可のダイアログを省略する。ただし、権限設定のdenyやaskのルールは引き続き評価される |
deny | ツールの呼び出しを取り消し、理由をClaudeに返す |
ask | 通常どおり、ユーザーに許可を確認する |
defer | 非対話モード(-p)で、呼び出しを保留して後で再開する。Agent SDKなどを使う連携向け |
PreToolUseのupdatedInputを使うと、ツールの引数を実行前に書き換えることもできます。このとき、入力オブジェクト全体が置き換わるため、変更しない項目も一緒に返す必要があります。
JSONで使える、全イベントに共通のフィールドもあります。
| フィールド | 内容 |
|---|---|
continue | falseにすると、フックの後でClaudeが処理を完全に止める。イベント固有の判断より優先される |
stopReason | continueがfalseのとき、ユーザーに表示するメッセージ |
systemMessage | ユーザーに表示する警告メッセージ |
terminalSequence | デスクトップ通知・ウィンドウのタイトル・ベルなどのために、Claude Codeに代わりに出力させる端末の制御文字列 |
イベントによって、判断を返す方法が違います。使うイベントのものを選びます。
| イベント | 判断の返し方 | 主なフィールド |
|---|---|---|
| UserPromptSubmit、PostToolUse、PostToolUseFailure、Stop、SubagentStop、ConfigChange、PreCompact | 最上位のdecision | "decision": "block"とreason |
| PreToolUse | hookSpecificOutput | permissionDecision、permissionDecisionReason |
| PermissionRequest | hookSpecificOutput | decision.behavior(allow / deny) |
| SessionStart、SubagentStart | 文脈の追加だけ | hookSpecificOutput.additionalContext |
| Notification、SessionEnd など | 判断はできない | ログや後片付けなど、副作用の用途 |
フィールドを置く階層を間違えると、黙って無視される
たとえばpermissionDecisionは、最上位ではなくhookSpecificOutputの中に置きます。階層を間違えるとJSONとしては読めるため、エラーが出ずに効果だけがなくなります。additionalContextも同じです。確かめるにはclaude --debugで起動し、ログに「Hook JSON output had unrecognized keys」という行がないかを探します。
Claudeに情報を足す
UserPromptSubmit・SessionStart・PostToolUseなどでは、additionalContextに文字列を入れて、Claudeに情報を渡せます。画面には表示されず、Claudeが次のリクエストで読みます。
{
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "Current branch: release-42. Deploy freeze until Friday."
}
}
公式ドキュメントは、ここに書く文を「事実の記述にする」よう勧めています。「デプロイ先は本番です」「このリポジトリはbun testを使います」のような書き方です。システムへの命令のような書き方をすると、Claudeがプロンプトインジェクション(外部の文章に混ざった不正な指示)を疑い、内容をユーザーに見せるだけで文脈として使わないことがある、という説明です。
また、additionalContext・systemMessage・標準出力の文字列は、それぞれ10,000文字が上限です。超えた分はファイルに保存され、Claudeには保存先のパスと先頭2,000文字ほどの抜粋だけが渡されます。Claudeに必ず見せたい内容は、上限の範囲に収めます。
複数のフックが動くとき
同じイベントで複数のフックが一致した場合は、すべて並列に実行され、全員が終わってから結果が合成されます。1つがdenyを返しても、ほかのフックの実行は止まりません。
PreToolUseで判断が食い違った場合は、deny > defer > ask > allowの順で、より制限の強い方が採用されます。additionalContextは、すべてのフックの分がまとめてClaudeに渡されます。
たとえば、Bashの実行前に「コマンドをログに追記するフック」と「rm -rfを止めるフック」を並べて書いたとします。rm -rf /tmp/buildが実行されようとすると、両方が並列で動きます。ログには記録され、ブロックするフックが2を返すので、コマンド自体は実行されません。「止めたものも記録に残る」わけです。
もう1つ重要なのは、同じ引数を書き換えるフックを複数置かないことです。複数のPreToolUseがupdatedInputで同じツールの入力を書き換えると、最後に終わったものが採用されます。並列で動くため、どれが最後になるかは決まっていません。
タイムアウト
フックには実行時間の上限があります。timeoutフィールドに秒数を書いて、フックごとに変更できます。
| 種類 | 既定の上限 |
|---|---|
| command・http・mcp_tool | 10分(UserPromptSubmitなど一部のイベントは30秒、MessageDisplayは10秒) |
| prompt | 30秒 |
| agent | 60秒 |
| SessionEnd(種類を問わず) | 全フックで合計1.5秒(timeoutを長く設定した場合は、最長60秒まで延びる) |
上限に達したフックは取り消され、その出力は捨てられます。PreToolUseでタイムアウトしても、ツールの呼び出しはブロックされず、通常の許可の流れで続行されます。「止めるフックが固まったら止まるだろう」とは期待できないので、止めることが目的のフックは、確実に素早く終わるように作ります。
よく使う例

ここでは、公式ガイドで紹介されている例を中心に、実際の業務でも使いやすいものを集めました。設定は、そのまま.claude/settings.json(そのプロジェクトだけ)か、~/.claude/settings.json(自分の全プロジェクト)に追記して使えます。
Bashスクリプトの例は、Mac・Linux、またはWindowsのGit Bashを前提にしています。Windowsでの書き方は、次の節で改めて説明します。
Windowsでは、matcherに注意
Git for Windowsを入れていないWindowsでは、Claude CodeがBashツールを登録せず、シェルコマンドはPowerShellツールで実行されます。公式ドキュメントによると、この環境でmatcherがBashだけのフックは、一度も動きません。コマンドを調べるフックは、Bash|PowerShellと書くと両方に対応できます。詳しくは次の節で説明します。
| 例 | イベント | 目的 | 難しさ |
|---|---|---|---|
| 1. 編集のたびに自動整形する | PostToolUse | コードの書式をそろえる | やさしい |
| 2. 重要なファイルを書き換えさせない | PreToolUse | 事故の防止 | ふつう |
| 3. 危険なコマンドを止める | PreToolUse | 事故の防止 | ふつう |
| 4. 圧縮後に前提を思い出させる | SessionStart | 前提の維持 | やさしい |
| 5. プロンプトに今の状況を足す | UserPromptSubmit | 情報の追加 | やさしい |
| 6. 実行したコマンドを記録する | PostToolUse | 履歴の確保 | やさしい |
| 7. 設定の変更を記録する | ConfigChange | 監査 | やさしい |
| 8. 終わる前にテストを確認する | Stop | 品質の確保 | ふつう |
| 9. 特定の許可を自動で承認する | PermissionRequest | 確認の手間を減らす | やや難しい |
例1:編集のたびに自動整形する
Claudeがファイルを編集するたびに、整形ツールのPrettierを実行して、書式をそろえます。手作業で整える手間と、Claudeに「整形して」と毎回頼む手間がなくなります。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
matcherにEdit|Writeを指定しているため、ファイルを書き換えるツールのときだけ動きます。コマンドの流れは、(1)jqで標準入力のJSONから編集されたファイルのパスを取り出し、(2)xargsでそのパスをPrettierに渡して整形する、というものです。
試すには、Claudeにシングルクォートの文字列を含む行をJavaScriptのファイルに追加してもらい、ファイルを開きます。Prettierの標準設定なら、ダブルクォートに書き換わっています。成功したフックは会話に何も表示しないので、整形されたかどうかはファイルで確認します。
Bashコマンドで書き換えられたファイルは対象外
Claudeは、シェルコマンド(sedやリダイレクトなど)でもファイルを変更できます。Edit|Writeに絞ったフックは、Bashコマンド経由の変更を検知できません。ファイルの変更を漏れなく見たい場合は、1ターンの終わりに作業ツリーを調べるStopフックにするか、FileChangedイベントを使います。
例2:重要なファイルを書き換えさせない
パスワードなどを書いた.envや、勝手に変わっては困るpackage-lock.json、.git/の中身を、Claudeが編集できないようにします。止めた理由はClaudeに返るので、Claudeは別の方法を探してくれます。
スクリプトは別ファイルに書きます。.claude/hooks/protect-files.shとして、次の内容を保存します。
#!/bin/bash
# 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
MacやLinuxでは、スクリプトに実行権限を付けます。
chmod +x .claude/hooks/protect-files.sh
そして、設定ファイルに登録します。$CLAUDE_PROJECT_DIRは、プロジェクトのルートを指す変数です。作業ディレクトリが変わっても、同じスクリプトを指せます。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}
試すには、Claudeに.envへコメントを1行足すよう頼みます。編集は実行されず、Blocked:で始まるメッセージがClaudeに返ります。
パターンを書き換えれば、仕事のファイルにも使えます。たとえば「確定済み」というフォルダの中を書き換えさせたくないなら、PROTECTED_PATTERNSに"/確定済み/"のように追加します。ファイルの場所が、プログラムのコードではなく文書や表計算データであっても、同じ考え方で守れます。
この種のフックは、権限モードを変えても効く
PreToolUseフックは、権限モードの確認よりも前に動きます。公式ドキュメントによると、denyを返すフックは、bypassPermissionsモードや--dangerously-skip-permissionsで起動した場合でもツールをブロックします。利用者が権限モードを変えても突破できない、組織のルールを作れるということです。
例3:危険なコマンドを止める
Bashツールでrm -rfが実行されようとしたら、止めます。matcherでBashツールに絞り、さらにifでrmで始まるコマンドに絞ると、関係のないコマンドでスクリプトが起動しません。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
"args": []
}
]
}
]
}
}
スクリプトは、標準入力のコマンドにrm -rfが含まれていたら、JSONでdenyを返します。
#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0 # 判断なし。通常の許可の流れに進む
fi
1つ目の例との違いは、終了コード2ではなく、JSONでdenyを返している点です。どちらでも止められます。理由を細かく返したい、updatedInputなども組み合わせたい、といった場合はJSONが便利です。
気をつけたいのは、rm -rfという文字列だけを探す方法では、rm -frやrm -r -fのような書き方をすり抜けてしまうことです。確実に守りたいときは、スクリプトの判定を丁寧に作るか、権限設定の拒否ルールと併用します。
例4:圧縮のあとも前提を思い出させる
会話が長くなると、Claude Codeは古いやりとりを要約して文脈を空ける「圧縮」を行います。この際に、大事な前提が抜け落ちることがあります。SessionStartにcompactのmatcherを付けると、圧縮のたびに前提を再び渡せます。
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo 'Reminder: use Bun, not npm. Run bun test before committing. Current sprint: auth refactor.'"
}
]
}
]
}
}
SessionStartフックの標準出力は、そのままClaudeの文脈に追加されます。上の例は固定の文を出していますが、git log --oneline -5のように、実行のたびに変わる出力にもできます。
毎回のセッション開始時に渡したいだけの内容は、フックではなくCLAUDE.mdに書くのが標準的な方法です。フックが向いているのは、「圧縮の直後」のようにタイミングが決まっているものです。
例5:プロンプトに今の状況を足す
プロンプトを送るたびに、現在のブランチ名など、実行時点でしか分からない情報を添えます。UserPromptSubmitフックの標準出力は、Claudeの文脈に追加されるため、echoだけで実現できます。
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "echo \"Current branch: $(git branch --show-current)\""
}
]
}
]
}
}
このイベントには、matcherがありません。書かなくても、プロンプトのたびに動きます。実行時間が長いと、プロンプトの処理全体が遅くなります。公式の既定では、UserPromptSubmitのタイムアウトは30秒です。軽いコマンドだけを置きます。
UserPromptSubmitは、自分が入力したプロンプトだけでなく、スケジュールされたタスクの実行や、バックグラウンドのサブエージェントからの報告のときにも動きます。
例6:実行したコマンドを記録する
Claudeが実行したBashコマンドを、すべてログファイルに追記します。あとから「何を実行させたのか」を確認できます。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt"
}
]
}
]
}
}
PostToolUseは実行後に動くので、記録されるのは実際に実行されたコマンドです。PreToolUseに置けば、実行前の記録になり、ブロックされたコマンドも残ります。
記録のためのフックは、async(後述)にしておくと、Claudeの作業を待たせずに済みます。
例7:設定の変更を記録する
ConfigChangeイベントは、セッション中に設定ファイルやスキルのファイルが変更されたときに動きます。外部のエディタやプロセスによる変更を、記録できます。コンプライアンス(法令・社内規則への対応)で、設定の変更履歴が必要な場合などに使います。
{
"hooks": {
"ConfigChange": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "jq -c '{timestamp: now | todate, source: .source, file: .file_path}' >> ~/claude-config-audit.log"
}
]
}
]
}
}
matcherで、user_settings・project_settings・local_settings・policy_settings・skillsのどれかに絞れます。変更を止めたいときは、終了コード2を返すか、{"decision": "block"}を返します。ただし、管理者のポリシー設定(policy_settings)の変更は、止められません。
例8:終わる前にテストを確認する
Stopは、Claudeが応答を終えるたびに動きます。ここで終了コード2を返すと、Claudeは止まらずに作業を続けます。標準エラー出力の文が、続きの作業の指示になります。テストが通るまで終わらせない、という使い方ができます。
#!/bin/bash
# .claude/hooks/check-tests.sh
INPUT=$(cat)
# すでにStopフックによる続行中なら、そのまま終わらせる(無限ループを防ぐ)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0
fi
if ! npm test --silent >/dev/null 2>&1; then
echo "npm test fails. Check the failures and fix them before finishing." >&2
exit 2
fi
exit 0
設定は、Stopにスクリプトを登録します。matcherには対応していません。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-tests.sh",
"args": []
}
]
}
]
}
}
スクリプトの最初でstop_hook_activeを確認しているのは、止める判断が堂々巡りにならないようにするためです。公式ドキュメントによると、Claude Codeは、Stopフックが8回続けて続行を指示すると、次のブロックを無視してターンを終わらせます。この回数は、Claudeがツールを呼び出すたびにリセットされます。フック自身が確認を入れておけば、無駄な繰り返しを避けられます。
回数の上限は、環境変数CLAUDE_CODE_STOP_HOOK_BLOCK_CAPで変更できます。
例9:決まった許可だけを自動で承認する
Claudeは、計画を示し終えるとExitPlanModeというツールを呼び出して、実行に進んでよいか尋ねます。この確認を毎回省きたい場合は、PermissionRequestフックで自動承認します。終了コードではなく、JSONでallowを返します。
{
"hooks": {
"PermissionRequest": [
{
"matcher": "ExitPlanMode",
"hooks": [
{
"type": "command",
"command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'"
}
]
}
]
}
}
承認されると、ダイアログが表示される代わりに、会話の記録に「Allowed by PermissionRequest hook」と出ます。
matcherはできるだけ狭くする
公式ドキュメントは「matcherを.*にするか空にすると、ファイルの書き込みやシェルコマンドを含む、すべての許可を自動承認してしまう」と警告しています。自動承認のフックは、必ずツール名を1つに絞って書いてください。
同じ仕組みで、権限モードを切り替えることもできます。updatedPermissionsにsetModeを入れると、そのセッションだけacceptEditsなどに切り替えられます。bypassPermissionsは、そのセッションがもともとバイパスモードを使える状態で始まっている場合にだけ有効です。
Windowsでの書き方

フックの解説は、MacやLinuxのBashを前提にしたものが多く、そのままWindowsに貼り付けると動かないことがあります。公式ドキュメントにはWindows向けの書き方も載っているので、Windowsで使う場合に引っかかりやすい点をまとめます。
どのシェルで動くか
コマンド型のフックのcommandは、シェル(コマンドを解釈して実行するソフト)に渡されて動きます。Windowsでは、次のように選ばれます。
| 状況 | フックのシェル | シェルコマンドを実行するツール |
|---|---|---|
| Git for Windowsを入れている | Git Bash(shellに"powershell"を指定すれば、PowerShellにもできる) | Bashツール(PowerShellツールが有効な場合は、PowerShellツールも) |
| Git for Windowsを入れていない | PowerShell | PowerShellツールのみ(Bashツールは登録されない) |
PowerShellは、PowerShell 7以降のpwsh.exeが見つかればそれを使い、なければWindows PowerShell 5.1のpowershell.exeを使います。
2行目の「Bashツールが登録されない」ことが、見落としやすいポイントです。この環境で、matcherにBashだけを書いたフックは、一度も動きません。コマンドの内容を調べるフックでは、Bash|PowerShellと書いて、両方のツールをカバーします。
PowerShellでフックを書く
個別のフックをPowerShellで動かすには、shellに"powershell"を指定します。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"shell": "powershell",
"command": "Write-Host 'File written'"
}
]
}
]
}
}
PowerShellのスクリプトファイル(.ps1)を実行させる場合は、次の2通りの書き方があります。
| 書き方 | 設定の例 | 特徴 |
|---|---|---|
引数リスト(args)で書く | "command": "powershell.exe" と "args": ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "${CLAUDE_PROJECT_DIR}/.claude/hooks/x.ps1"] | シェルを介さず直接起動する。引用符やスペースの問題が起きにくい。公式の例もこの形 |
| シェル形式で書く | "shell": "powershell" と "command": "& \"$env:CLAUDE_PROJECT_DIR\\.claude\\hooks\\x.ps1\"" | パイプなどシェルの機能が使える |
引数リストの例にある-NoProfileは、PowerShellのプロファイル(起動時に読み込まれる設定)を読まないことを指定する引数です。起動が速くなります。-ExecutionPolicy Bypassは、ローカルのスクリプトファイルを実行できるようにする指定です。
PowerShellでは、変数の書き方に注意
プロジェクトのルートを指す変数は、PowerShellのシェル形式では${CLAUDE_PROJECT_DIR}か$env:CLAUDE_PROJECT_DIRと書きます。Bashと同じ$CLAUDE_PROJECT_DIRと書くと、PowerShellは「未定義のローカル変数」と解釈して空になり、スクリプトのパスが途中で切れてしまいます。また、${CLAUDE_PROJECT_DIR}はダブルクォートの中では使えますが、シングルクォートの中では展開されません。
例:PowerShellで書いたファイル保護
前の節の「重要なファイルを書き換えさせない」フックを、PowerShellで書くと次のようになります。jqは使わず、PowerShellが標準で持つConvertFrom-JsonでJSONを読みます。.claude/hooks/protect-files.ps1として保存します。
# .claude/hooks/protect-files.ps1
$callInput = [Console]::In.ReadToEnd() | ConvertFrom-Json
$path = $callInput.tool_input.file_path -replace '\\', '/'
$patterns = @('.env', 'package-lock.json', '/.git/')
foreach ($pattern in $patterns) {
if ($path -like "*$pattern*") {
[Console]::Error.WriteLine("Blocked: $path matches protected pattern '$pattern'")
exit 2
}
}
exit 0
設定ファイルには、次のように登録します。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/protect-files.ps1"
]
}
]
}
]
}
}
危険なコマンドを止めるフックでは、BashツールとPowerShellツールの両方を対象にします。ifは1つのルールしか書けないため、ツールごとにハンドラーを分けます。公式ドキュメントの例を簡略にすると、次の形です。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|PowerShell",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "powershell.exe",
"args": ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"]
},
{
"type": "command",
"if": "PowerShell(Remove-Item *)",
"command": "powershell.exe",
"args": ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"]
}
]
}
]
}
}
パスの区切りに注意する
WindowsではClaude Codeが渡すファイルのパスが、C:\project\src\index.tsのようにバックスラッシュ区切りになります。Git Bashでフックを動かしていても同じです。
そのため、スクリプトの中で/src/のようにスラッシュ区切りで比較すると、どのパスにも一致せず、ブロックしたいつもりの操作が素通りします。公式ドキュメントでも、この点が警告されています。比較の前に区切りをそろえます。
| 言語 | 書き方 |
|---|---|
| Bash | FILE_PATH="${FILE_PATH//\\//}" |
| PowerShell | $path = $callInput.tool_input.file_path -replace '\\', '/' |
| Python | file_path.replace("\\", "/") |
また、パスは絶対パスで渡されます。^で先頭を固定して比較するのではなく、/src/のようにパスの途中の区切りを含めた部分一致で見るのが公式の推奨です。
jqが入っていないとき
公式のBashの例は、JSONの読み取りにjqを使います。jqが入っていない環境では、「jq: command not found」というエラーになります。対処は次の3つです。
jqをインストールして、PATHの通った場所に置く- PowerShellの
ConvertFrom-Jsonで読む(前の例) - PythonやNode.jsで読む(公式ガイドもこの方法を挙げています)
社内の複数のパソコンに配る予定なら、追加のインストールが要らないPowerShellで書くのが、手間が少なくて済みます。
.cmdや.batのコマンドを呼ぶとき
引数リスト(args)を使う形式では、commandに実行ファイル(.exeなど)を指定する必要があります。npm・npx・eslintなどがnode_modules/.binに置く.cmdや.batのファイルは、シェルなしでは起動できません。公式ドキュメントは、次のどちらかを勧めています。
nodeと、その下のスクリプトのパスを指定する(例:"command": "node", "args": ["…/eslint/bin/eslint.js"])。node.exeは本物の実行ファイルなので、全OSで動きますargsを使わず、シェル形式で.cmdの名前をそのまま書く
Git Bashの設定ファイルが出力を邪魔する
Git Bashなどでは、非対話モードでもプロファイル(~/.bashrcなど)が読み込まれる場合があります。プロファイルにechoが書かれていると、その文字がフックの標準出力の先頭に混ざり、JSONとして読まれなくなります。しかも終了コードは0のため、エラーも出ません。
確認するにはclaude --debugで起動し、ログに「Hook output does not start with {」という行がないかを探します。直すには、echoを対話モードのときだけ動くように囲みます。
# ~/.bashrc の例
if [[ $- == *i* ]]; then
echo "Shell ready"
fi
Windowsでよくあるつまずき
| 症状 | 原因 | 対処 |
|---|---|---|
| フックがまったく動かない | Git for Windowsがなく、Bashツールが登録されていない。matcherがBashだけ | Bash|PowerShellにする |
| ブロックしたいのに通ってしまう | パスがバックスラッシュ区切りで、スラッシュ区切りの比較に一致しない | 比較の前に区切りをそろえる |
jq: command not found | jqが入っていない | jqを入れるか、PowerShell・Python・Node.jsで書く |
| スクリプトのパスが途中で切れる | PowerShellで、$CLAUDE_PROJECT_DIRと書いている | ${CLAUDE_PROJECT_DIR}か$env:CLAUDE_PROJECT_DIRにする |
.cmdを起動できない | 引数リスト形式では、シェルなしで起動できない | nodeでスクリプトを直接指定するか、シェル形式にする |
| JSONを返したのに効かない | プロファイルのechoが標準出力に混ざっている | echoを対話モードのときだけ動くようにする |
| 通知のダイアログが見えない | ダイアログがターミナルの後ろに開いている | PowerShellで同じコマンドを直接実行して確かめる |
| SessionStartで環境変数が反映されない | PowerShellで動くフックは、CLAUDE_ENV_FILEを使えない | Bashで動かすか、別の方法で環境を整える |
フックの種類・非同期実行

ここまでは、シェルコマンドを実行する「コマンド型」を中心に説明してきました。フックには、全部で5つの種類があります。
type | 何をするか | 向いている場面 |
|---|---|---|
command | シェルコマンドを実行する | 整形・通知・ファイルの保護など、手元のパソコンでの処理 |
http | イベントの情報をURLにPOSTで送る | 社内の監査サービスなど、外部サービスに判断や記録を任せる |
mcp_tool | 設定済みのMCPサーバーのツールを呼び出す | セキュリティスキャンなど、MCPサーバーに用意された処理を使う |
prompt | Claudeのモデルに1回だけ判断させる | 決まったルールでは書けない、文脈による判断 |
agent | ツールを使えるサブエージェントに調べさせて判断させる(実験的) | ファイルやテスト結果を実際に確かめてからの判断 |
promptフック:判断をモデルに任せる
「すべてのタスクが完了しているか」のように、ルールでは書きにくい判断は、prompt型で行います。シェルコマンドの代わりに、プロンプトとフックの入力情報がClaudeのモデルに送られ、モデルが{"ok": true}か{"ok": false, "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\"}."
}
]
}
]
}
}
okがfalseのときの動きは、イベントによって違います。
| イベント | "ok": falseのときの動き |
|---|---|
| Stop、SubagentStop | reasonがClaudeに返り、作業が続く。"impossible": trueを添えると、満たせない条件として止まることを許可する |
| PreToolUse | ツールの呼び出しが拒否される。既定ではターンが終わり、理由は警告として表示される。continueOnBlock: trueを付けると、理由がツールのエラーとしてClaudeに返り、作業が続く |
| PostToolUse | 既定ではターンが終わり、理由が警告として表示される。continueOnBlock: trueで、理由をClaudeに返して続行する |
| UserPromptSubmit など | ターンが終わり、理由が警告として表示される |
使うモデルは、modelフィールドで指定できます。省略すると、Claude Codeがバックグラウンドの処理に使うモデルが使われます。タイムアウトの既定は30秒です。
なお、公式ドキュメントでは、/goalコマンドが「セッション内で有効なpromptフック(Stop)の組み込みのショートカット」と説明されています。設定を書かずに「この条件を満たすまで作業を続けて」と指示したいときは、こちらが手軽です。
agentフック:確かめてから判断する
promptフックはフックの入力情報だけで判断します。実際のファイルやテストの結果を確かめる必要があるときは、agent型を使います。サブエージェントが起動し、ファイルの読み込みや検索、コマンドの実行をしてから、okで答えます。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
"timeout": 120
}
]
}
]
}
}
$ARGUMENTSは、フックの入力JSONに置き換えられるプレースホルダーです。タイムアウトの既定は60秒、ツールを使えるターン数は最大50です。
agentフックは実験的な機能
公式ドキュメントでは、agentフックは実験的で、動作や設定が今後のリリースで変わる可能性があると明記されています。本番の運用では、コマンド型のフックを優先するよう案内されています。
httpフック:外部のサービスに任せる
httpフックは、コマンド型が標準入力で受け取るのと同じJSONを、指定したURLへPOSTで送ります。チーム全体の操作を1つの監査サービスに集めたい、といった場合に使います。
{
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/tool-use",
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
}
応答は、コマンド型のJSON出力と同じ形式で返します。ステータスコードの扱いは、次のとおりです。
- 2xx:本文のJSONを解釈する。ブロックしたいときは、2xxの応答の本文に判断のフィールドを入れる
- 2xx以外・接続失敗:非ブロックのエラー。動作はそのまま進む。ステータスコードだけではブロックできない
- リクエストに失敗したときに動作を止めたい場合は、
onFailure: "block"を指定する
ヘッダーの値には環境変数を入れられますが、allowedEnvVarsに名前を書いたものだけが展開されます。書いていない変数は空文字になります。なお、イベントのJSONには、実行するコマンドやプロンプトの内容が含まれます。送信先は、信頼できる自社のサービスに限定してください。組織側でallowedHttpHookUrlsによって、送信を許可するURLを制限することもできます。
mcp_toolフック:MCPサーバーの処理を呼ぶ
設定済みのMCPサーバーのツールを、フックから呼び出せます。たとえば、ファイルを編集するたびに、サーバーのセキュリティスキャンのツールにファイルのパスを渡す設定は次のとおりです。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "mcp_tool",
"server": "my_server",
"tool": "security_scan",
"input": { "file_path": "${tool_input.file_path}" }
}
]
}
]
}
}
注意点は、セッションの開始直後(起動時のSessionStartとSetup)には、MCPサーバーがまだ使えないため、mcp_toolのフックは実行されずにスキップされることです。起動時に必要な処理は、コマンド型で書きます。
バックグラウンドで動かす(async)
通常のフックは、終わるまでClaudeの作業を待たせます。時間のかかる処理(テストの実行、外部サービスへの送信など)は、"async": trueを付けると、バックグラウンドで動かせます。コマンド型のフックだけで使えます。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "/path/to/run-tests.sh",
"async": true
}
]
}
]
}
}
非同期のフックには、次の特徴と制約があります。
- 止められない:実行が終わった後に結果が出るため、
decisionやpermissionDecision、continueは効きません - 結果は次のターンで届く:フックが終わると、JSONの
additionalContextとsystemMessageが、次の会話のターンでClaudeに渡されます。セッションが待機中のときは、次にユーザーが操作するまで待ちます - タイムアウトは効かない:バックグラウンドで動いている間、
timeoutは適用されません - 非対話モードでは中断される:
-pで動かしている場合、終了時に実行中の非同期フックは取り消されます - 実行ごとにプロセスができる:呼ばれるたびに、別のバックグラウンドのプロセスが起動します
「失敗したときだけClaudeを呼び起こしたい」場合は、asyncRewakeを使います。バックグラウンドで動かし、終了コード2で終わったときに、標準エラー出力(なければ標準出力)をClaudeに渡して目を覚まさせます。
スキルやサブエージェントに入れる
フックは、設定ファイルだけでなく、スキルやサブエージェントの定義ファイルの冒頭(フロントマター)にも書けます。そのスキルを使っている間、またはそのサブエージェントが動いている間だけ有効にしたいときに便利です。
---
name: secure-operations
description: Perform operations with security checks
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/security-check.sh"
---
スキルのフックは、スキルが呼び出されたときに登録され、セッションの残りの間動き続けます。最初に1回成功したら外したい場合は、once: trueを付けます。サブエージェントのフックは、そのサブエージェントが動いている間だけ有効で、終わると外れます。サブエージェント内のStopフックは、SubagentStopとして扱われます。
安全に使うための注意

フックは便利な反面、パソコンの上でコマンドを自動実行する仕組みです。設定を間違えたり、中身を確かめずに他人のフックを取り込んだりすると、被害が大きくなります。使い始める前に、次の点を確認しておきましょう。
フックは自分の権限で動く
公式ドキュメントは、コマンド型のフックについて、ユーザーの全権限でシェルコマンドを実行すると警告しています。ユーザーのアカウントがアクセスできるファイルなら、変更も削除もできるということです。追加するフックは、必ず自分で中身を確認し、先に試してから有効にします。
公式ドキュメントは、フックを書く際の基本姿勢として、次の5つを挙げています。
| 基本 | 具体的には |
|---|---|
| 入力を鵜呑みにしない | 受け取ったJSONの値を検証・無害化してから使う |
| シェル変数は必ず引用符で囲む | $VARではなく"$VAR"と書く。ファイル名にスペースや特殊な文字が入っていても壊れにくくなる |
パスの..を拒否する | ファイルのパスに..が含まれていないかを確認し、想定外の場所へ出ていくのを防ぐ |
| 絶対パスを使う | スクリプトの場所は、${CLAUDE_PROJECT_DIR}などで絶対パスにする |
| 機密のファイルを避ける | .env、.git/、鍵のファイルなどを、フックの処理対象にしない |
他人のリポジトリのフックに注意する
プロジェクトの.claude/settings.jsonに書かれたフックは、そのプロジェクトを開くと動きます。他人が作ったリポジトリを開くときは、フックにも注意が必要です。
| 実行のしかた | フックが動くまで |
|---|---|
| 対話モード(ふつうの使い方) | フォルダの「ワークスペースの信頼」ダイアログを承認するまで、あなた自身の~/.claude/settings.jsonを含め、すべての設定ファイルのフックが保留される |
-pやAgent SDKでの実行 | ダイアログは表示されず、フォルダは信頼されたものとして扱われる。リポジトリにコミットされたフックが、確認なしに動く |
見知らぬリポジトリにclaude -pを向ける前には、.claude/の設定ファイルを確認してください。公式ドキュメントは、--bareを付けて起動するか、--settings '{"disableAllHooks": true}'でフックを止めて実行するよう案内しています。
プロジェクトのスキルやサブエージェントにもフックが入っている
フックは、設定ファイルだけでなく、プロジェクトのスキルやサブエージェントの冒頭にも書けます。公式ドキュメントによると、プロジェクトのサブエージェントのフックは、そのフォルダの信頼を承認した後でなければ動きません。ただし、プロジェクトのスキルのフックは、スキルが呼び出された時点で登録されます。取り込んだスキルやサブエージェントの中身も、確認の対象に含めてください。
権限設定との関係
フックと、ふだんの許可ダイアログや権限設定(allow・deny)は、次のように組み合わさります。
| 場面 | 動き |
|---|---|
フックがdenyを返す | 権限モードに関係なく、ツールがブロックされる。bypassPermissionsや--dangerously-skip-permissionsでもブロックされる |
フックがallowを返す | 許可ダイアログは省略されるが、権限設定のdenyやaskのルールは引き続き有効。組織が設定したdenyリストも効く |
| フックが何も返さない(終了コード0) | 「許可した」ことにはならない。通常の許可の流れに進む |
| 設定ファイルのフックで緩めようとする | 設定ファイルやプラグインのフックは、権限ルールが許す範囲を超えて制限を緩めることはできない。厳しくするほうにだけ働く |
つまり、フックは「制限を足す」ことには強く、「制限を外す」ことは苦手です。権限設定のdenyを回避するフックは作れません。基本のルールは権限設定で決め、権限設定では表現できない条件をフックで足す、という役割分担になります。
フックが失敗したとき、動作を止めるか
通常、フックが起動に失敗したり、タイムアウトしたりしても、ツールの呼び出し自体は実行されます。ポリシーのためのフックで、スクリプトのパスを間違えていると、何も止まらないままになるということです。
これを防ぐために、コマンド型とhttp型のフックには"onFailure": "block"を指定できます。失敗やタイムアウトが起きたときに、そのイベントで終了コード2を返したのと同じ動き(PreToolUseならツールの呼び出しのブロック)になります。この指定には、Claude Code v2.1.295以降が必要です。
{
"type": "command",
"command": "node",
"args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/check-command.js"],
"onFailure": "block"
}
ただし、Stop・SubagentStop・TaskCompleted・TeammateIdleのフックと、バックグラウンド(async)のフックには効果がありません。また、公式ドキュメントは、ポリシーのフックを設定したあとは、最初の実行で「hook error」の通知が出ていないかを必ず確かめるよう勧めています。パスの書き間違いが、いちばん多い原因です。
フックを無効にする・制限する
フックを一時的にすべて止めたいときは、設定ファイルに"disableAllHooks": trueを書きます。次の点に注意してください。
- 個別に無効にする方法はない:1つだけ止めたい場合は、設定からその項目を削除します
- 設定の優先順位に従う:プロジェクトの
.claude/settings.jsonにfalseがあると、ユーザー設定のtrueを上書きできます - 管理者のフックは止められない:管理者のポリシー設定で定義されたフックは、ユーザー・プロジェクト・ローカルの設定の
disableAllHooksでは止められません - 1回の実行だけ止めたい:
--settings '{"disableAllHooks": true}'で起動します
管理者は、管理設定のallowManagedHooksOnlyで、動かせるフックを制限できます。有効にすると、ユーザー・プロジェクト・ローカル・プラグインのフックは動かなくなります。
動かないときの確かめ方
フックが期待どおりに動かないときは、症状から原因を絞ります。
| 症状 | 確かめること |
|---|---|
| フックが動かない | /hooksで、正しいイベントの下に出ているか。matcherの大文字・小文字(区別される)。イベントの選択(PreToolUseは実行前、PostToolUseは実行後)。ifを、ツール系以外のイベントに書いていないか |
/hooksに出てこない | JSONの文法(末尾のカンマやコメントは不可)。設定ファイルの場所。数秒たっても反映されなければセッションを開き直す。「Only hooks from managed settings run here」と出ているなら、組織がallowManagedHooksOnlyを設定している |
| 「hook error」と表示される | スクリプトが想定外のコードで終わっている。サンプルのJSONをパイプで渡して、手で試す(下記)。「command not found」なら絶対パスか${CLAUDE_PROJECT_DIR}を使う。実行権限(chmod +x) |
| JSONを返しても効かない | プロファイルのechoが先頭に混ざっていないか。フィールドの階層(hookSpecificOutputの内側か)。claude --debugで確認する |
| Stopフックが止まらない | stop_hook_activeを確認しているか。8回続けてブロックすると、Claude Codeが上書きして終わらせる |
スクリプトを手で試すには、サンプルのJSONを標準入力で渡し、終了コードを見ます。
echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh
echo $?
Claude Codeの画面では、Ctrl+Oでトランスクリプト(会話の記録の画面)を開くと、フックの結果を確認できます。成功したフックは、通常は何も表示されません。ブロックされた場合は、理由のメッセージが見えます。
さらに詳しい実行内容(終了コード・標準出力・標準エラー出力)は、デバッグログに記録されます。claude --debug-file /tmp/claude.logのようにログの場所を指定して起動するか、起動後に/debugを実行してログを有効にします。--debugだけでは端末に出力されず、ログは~/.claude/debug/の下のファイルに書かれます。
遅いフックを探すには
デバッグログには、フックごとに「Hooks: PostToolUse:Write [コマンド] finished with status 0 (31ms)」のような、かかった時間つきの行が残ります。公式ドキュメントによると、この形式はClaude Code v2.1.296以降です。毎回のやりとりを遅くしているフックは、この時間から見つけられます。
よくある質問
フックとCLAUDE.mdは、どちらを使えばいいですか?
Claudeに前提や方針を伝えたいだけなら、CLAUDE.mdが向いています。毎回例外なく実行したい処理(整形、通知、特定のファイルの保護など)は、フックにします。迷ったときは、まずCLAUDE.mdに書いてみて、守られないことがあれば、その部分をフックにする進め方が安全です。
フックは、どの画面で使えますか?
公式ドキュメントでは、ターミナル、IDE拡張、デスクトップアプリ、クラウド上のセッションのいずれでも、同じイベントでフックが動くと説明されています。ただし、クラウド上のセッションは手元の~/.claude/settings.jsonを読みません。クラウドでも使いたいフックは、プロジェクトの.claude/settings.jsonに書きます。
フックの設定を変えたら、Claude Codeの再起動は必要ですか?
設定ファイルを直接編集した場合は、通常はファイルの監視によって自動で反映されます。反映されないときは、セッションを開き直してください。/hooksで、設定が読み込まれているかを確認できます。
1つだけ一時的に止めることはできますか?
個別のフックを無効にする仕組みは、公式にはありません。設定ファイルからその項目を削除するか、すべてのフックを止めるdisableAllHooksを使います。
複数のフックを書いたら、順番に動きますか?
順番には動きません。同じイベントに一致するフックは、並列で実行されます。順序に依存する処理は、1つのスクリプトにまとめてください。判断が食い違った場合は、PreToolUseではdenyが最も優先されます。
フックで、Claudeに指示を出して作業を続けさせられますか?
できます。Stopフックで終了コード2を返すか、"decision": "block"とreasonを返すと、Claudeは止まらずに、その理由を次の指示として作業を続けます。additionalContextで、エラーではない形の補足を渡すこともできます。いずれも、8回続けて繰り返すと上書きされる点に注意します。
Windowsでも使えますか?
使えます。Git for Windowsを入れていない場合は、PowerShellでフックが動き、Bashツールもありません。matcherはBash|PowerShellと書き、スクリプトはPowerShellで書くのが確実です。パスがバックスラッシュ区切りで渡される点にも注意してください。詳しくは「Windowsでの書き方」の節にまとめています。
フックを書くのが難しそうです。Claudeに作ってもらえますか?
公式ガイドに「やりたいことを説明すれば、Claudeがフックを書いてくれる」と書かれています。やりたいことと、設定する場所(プロジェクトか、自分の全体か)を伝えて頼み、できた設定は/hooksと実際の動作で確かめてから使い始めます。
フックで、Claudeに渡る内容を書き換えられますか?
イベントによって異なります。PreToolUseでは、updatedInputでツールの引数を実行前に書き換えられます。PostToolUseでは、updatedToolOutputで、Claudeが見るツールの結果を置き換えられます。ただし、結果の書き換えは、Claudeに見える内容が変わるだけで、実行済みの処理は取り消せません。UserPromptSubmitでは、プロンプトそのものは書き換えられず、additionalContextで情報を添えることだけができます。
チームで同じフックを使いたいときは?
プロジェクトの.claude/settings.jsonに書いてリポジトリにコミットすると、全員に共有されます。スクリプトも.claude/hooks/に置いて、一緒にコミットします。複数のプロジェクトに配りたい場合は、プラグインのhooks/hooks.jsonにまとめる方法もあります。組織全体に強制したい場合は、管理者がポリシー設定に書きます。
ファイルを編集した後のフックが、Bashで書き換えたファイルでは動きません。
仕様です。Edit|Writeに一致するPostToolUseフックは、Bashコマンドや、Claude Code以外のプロセスが書き換えたファイルでは動きません。漏れなく検知したいときは、ファイルの変更を検知するFileChangedイベントを使うか、Stopフックで、1ターンの終わりに作業ツリーをまとめて調べます。
フックに渡る情報には、何が含まれますか?
イベントごとに違いますが、作業ディレクトリ、セッションID、ツールの名前と引数(実行しようとしているコマンドや、書き込もうとしているファイルのパス、内容)、送信したプロンプトの文などが含まれます。httpフックでは、それらがPOSTの本文として外部のURLに送られるため、送信先は信頼できるサービスに限ります。
ブロックしたプロンプトは、記録に残りますか?
残る場合があります。UserPromptSubmitのフックでブロックしても、プロンプトの文は、セッションの記録ファイルや入力履歴などに残ることがあると公式ドキュメントに書かれています。ブロックは、機密情報をディスクに残さないための手段ではありません。
まとめ
この記事のまとめ
- フックは、Claude Codeの動作の決まったタイミングでコマンドなどを自動実行する仕組み。Claudeの判断に関係なく、条件が合えば毎回動く
- 設定は
settings.jsonに「イベント → matcher → 実行する処理」の3段で書く。最初はNotificationの通知フックで試すと分かりやすい - ブロックするときは終了コード2か、JSONの
denyを使う。終了コード1では止まらない - 整形・ファイルの保護・危険なコマンドの阻止・記録・終了前の確認など、「例外なく行いたい処理」に向く。基本の許可・拒否のルールは権限設定で決める
- Windowsでは、Bashツールがない環境では
matcherにBash|PowerShellと書き、パスのバックスラッシュとjqの有無に注意する - フックはユーザーの権限で動く。中身を確認してから有効にし、他人のリポジトリは、動かす前に
.claude/を確かめる
フックは、Claude Codeを「お願いして使うAI」から「決まったルールのもとで動くAI」に変える設定です。最初は通知のような影響の小さいものから始めて、動きを確かめながら、ファイルの保護や記録などに広げていくのがおすすめです。
チームや社内でClaude Codeを使うときは、どの操作を止めるか、どこまでを自動化するか、設定をどう共有するかを決めておくと、運用が安定します。INTERCREATE JAPANでは、Claude Code・Codexの導入から、社内ルールに合わせたフックやスキルの設計、運用の定着までをお手伝いしています。
参考・出典
INTERCREATE JAPAN
