フックの使い方
フックで編集後の整形・通知・保護ファイルのブロック・コンテキストの再注入などを自動化する手順と、よくある用途の設定例、動かないときの対処をまとめます。
フック(hook)は、Claude Code のライフサイクルの決まった時点で動く、ユーザー定義のシェルコマンドです。Claude が実行するかどうかを選ぶのではなく、特定の処理が必ず実行されるので、プロジェクトの規則の強制・繰り返し作業の自動化・既存ツールとの連携に使えます。イベントごとの入出力の仕様や全フィールドはフックのリファレンスにあり、このページは使い方と実例を扱います。
要点#
- 設定ファイルの
hooksに、イベント名・matcher・実行するコマンドを書く - 終了コード 2 でその操作をブロックでき、stderr の内容が Claude へのフィードバックになる
- 終了コード 0 で JSON を stdout に出すと、許可・拒否・文脈の追加など細かい制御ができる
- 判断が要る場面には、LLM が評価する
prompt型・agent型のフックも使える - 外部のサービスにイベントを送る
http型もある /hooksで設定済みのフックを一覧できる
最初のフックを作る#
Claude が入力待ちになったときにデスクトップ通知を出すフックを作ります。
~/.claude/settings.jsonを開き、Notificationフックを足します(ファイルが無ければ作ります)。次は macOS 用のosascriptの例です。Linux と Windows は後述の「入力待ちを通知で知る」を参照してください
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}
すでに hooks キーがあるときは、全体を置き換えず、既存のイベントのキーと並べて Notification を足します。イベント名は、1つの hooks オブジェクトの中のキーです。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }]
}
],
"Notification": [
{
"matcher": "",
"hooks": [{ "type": "command", "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'" }]
}
]
}
}
- Claude Code のプロンプトで
/hooksを入力してフックのブラウザを開きます。新しいフックがNotificationの下に出ます - Esc で戻り、Shift+Tab を押して、ステータスバーに
⏸ manual mode onが出るまで切り替えます。権限が要る作業を Claude に頼み、ターミナルから離れると、デスクトップ通知が届きます
CLI で希望を説明して、Claude にフックを書かせることもできます。
よくある用途#
どの例も、そのまま設定ファイルに足せる設定です。フックが動くイベントの全一覧はフックのリファレンスにあります。
入力待ちを通知で知る#
Claude が作業を終えて入力を待っているとき、デスクトップ通知を出します。ターミナルを見続けなくても、別の作業に移れます。Notification イベントを使います。~/.claude/settings.json に足します。
macOS:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}
- 通知が出ないとき:
osascriptは内蔵の Script Editor アプリを通して通知します。Script Editor に通知の権限が無いと、コマンドは黙って失敗し、macOS は許可を求めません。Terminal で一度osascript -e 'display notification "test"'を実行すると、Script Editor が通知設定の一覧に現れます。その段階では何も表示されません。「System Settings > Notifications」で「Script Editor」を探し、「Allow Notifications」をオンにして、もう一度試します
Linux:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
}
]
}
]
}
}
- 通知が出ないとき:
notify-sendはデスクトップの通知デーモンが要ります。ヘッドレスサーバー・SSH セッション・多くのコンテナには無いので、先にコマンドを直接notify-send 'Claude Code' 'test'と試します。コマンドが見つからなければ、Debian・Ubuntu ではlibnotify-binパッケージ(またはディストリビューションの相当品)を入れます
Windows(PowerShell):
{
"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')\""
}
]
}
]
}
}
- ダイアログが出ないとき:このコマンドは画面の隅の通知ではなくダイアログボックスを開くので、ターミナルの裏に隠れることがあります。先に PowerShell でコマンドを直接試します。WSL で Claude Code を動かすなら、Windows との相互運用で
powershell.exeがPATHから使える必要があります
空の matcher は、すべての通知の種類で発火します。特定の種類だけに絞るには、permission_prompt(権限の承認待ち)や idle_prompt(応答後しばらく入力が無い)などを matcher に入れます。値の全一覧はフックのリファレンスの Notification の節にあります。
編集のたびにコードを自動整形する#
Claude が編集したファイルに、毎回 Prettier を自動で走らせ、手作業なしで書式をそろえます。PostToolUse イベントを Edit|Write の matcher で使うので、ファイルを編集するツールの後だけ動きます。コマンドは、jq で編集されたファイルのパスを取り出して Prettier に渡します。プロジェクトルートの .claude/settings.json に足します。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
- 試すには、JavaScript ファイルにシングルクォートの文字列を含む行を足すよう頼み、ファイルを開きます。Prettier の既定の設定なら、フックがダブルクォートに書き換えます
- フックが成功したとき、会話には何も表示されません。動いたかは、ファイルが整形されたかで確かめるか、後述の「デバッグ」を使います
- 特定のファイルを、
Bashコマンドでの書き換えを含め、どう変わっても整形し直したいなら、代わりにFileChangedフックを使います
補足
このページの Bash の例は、JSON の解析に jq を使います。macOS は brew install jq、Debian・Ubuntu は apt-get install jq で入ります。
保護したファイルへの編集をブロックする#
.env・package-lock.json・.git/ 以下など、機密や生成物のファイルを Claude に変更させません。ブロックされた理由は Claude へのフィードバックになり、Claude は方針を変えられます。フックが呼ぶ別ファイルのスクリプトで、対象のパスを保護パターンの一覧と照合し、終了コード 2 で編集をブロックします。
.claude/hooks/protect-files.shに保存します
#!/bin/bash
# protect-files.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Normalize Windows backslash separators so the patterns below match
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
- macOS と Linux では、スクリプトに実行権限を付けます(付けないと Claude Code は実行できません)
chmod +x .claude/hooks/protect-files.sh
.claude/settings.jsonに、EditとWriteの呼び出しの前にスクリプトを動かすPreToolUseフックを登録します
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}
.envファイルにコメントを足すよう Claude に頼みます。Claude Code は編集を実行前にブロックし、スクリプトのBlocked:のメッセージをフィードバックとして Claude に渡します
コンパクション後に文脈を再注入する#
コンテキストウィンドウが埋まると、コンパクションが会話を要約して空きを作りますが、大事な詳細が失われることがあります。compact の matcher を持つ SessionStart フックで、コンパクションのたびに重要な文脈を再注入します。コマンドが stdout に書いたプレーンテキストは、Claude の文脈に加わります。次の例は、プロジェクトの規約と最近の作業を Claude に思い出させます。プロジェクトルートの .claude/settings.json に足します。
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo 'Reminder: use Bun, not npm. Run bun test before committing. Current sprint: auth refactor.'"
}
]
}
]
}
}
echoは、動的な出力を作る任意のコマンド(最近のコミットを出すgit log --oneline -5など)に置き換えられます- セッション開始のたびに文脈を入れたいなら、CLAUDE.md のほうが向いています。環境変数についてはフックのリファレンスの
CLAUDE_ENV_FILEを参照してください
設定の変更を記録する#
セッション中に設定ファイルやスキルのファイルが変わったことを記録します。ConfigChange イベントは、外部のプロセスやエディタが設定ファイルを変更したときに発火するので、コンプライアンスのために変更を記録したり、許可されていない変更をブロックしたりできます。次の例は、変更のたびに監査ログへ追記します。~/.claude/settings.json に足します。
{
"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"}を返します - 確かめるには、セッション中に別のエディタで設定ファイルを編集し、
~/claude-config-audit.logを開きます。フックが、変更ごとに、タイムスタンプ・変更元・ファイルパスを入れた JSON を1行ずつ追記します
ディレクトリやファイルが変わったら環境を読み込み直す#
プロジェクトによっては、いるディレクトリごとに環境変数が違います。direnv のようなツールはシェルで自動的にこれを行いますが、Claude の Bash ツールはその変化を自分では拾いません。
SessionStart フックと CwdChanged フックを組み合わせると解決します。SessionStart が起動したディレクトリの変数を読み込み、CwdChanged が Claude のディレクトリ移動のたびに読み込み直します。どちらも CLAUDE_ENV_FILE に書き込み、Claude Code はそれを各 Bash コマンドの前にスクリプトの前置きとして実行します。~/.claude/settings.json に足します。
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
}
]
}
],
"CwdChanged": [
{
"hooks": [
{
"type": "command",
"command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
}
]
}
]
}
}
.envrcのある各ディレクトリで、一度direnv allowを実行して、direnv に読み込みを許可します- direnv の代わりに devbox や nix を使うなら、
direnv export bashをdevbox shellenvかdevbox global shellenvに置き換えれば、同じ形が使えます - ディレクトリの変更すべてでなく特定のファイルに反応するには、
FileChangedを使い、matcherに監視するファイル名を|で区切って並べます。監視リストを作るとき、Claude Code はこの値を、正規表現として評価せず、リテラルのファイル名に分けます。次の例は、作業ディレクトリの.envrcと.envを監視します
{
"hooks": {
"FileChanged": [
{
"matcher": ".envrc|.env",
"hooks": [
{
"type": "command",
"command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
}
]
}
]
}
}
入力のスキーマ・watchPaths の出力・CLAUDE_ENV_FILE の詳細は、フックのリファレンスの CwdChanged と FileChanged の節にあります。
特定の権限確認を自動で承認する#
いつも許可しているツール呼び出しの承認ダイアログを省きます。次の例は、Claude が計画を示して進めてよいか尋ねるときに呼ぶ ExitPlanMode を自動承認し、計画ができるたびに確認されないようにします。
上の終了コードの例と違い、自動承認では、フックが JSON の判定を stdout に書く必要があります。Claude Code は、権限を尋ねようとするとき PermissionRequest フックを実行し、フックが "behavior": "allow" を返せば、あなたの代わりに答えます。matcher で ExitPlanMode だけに絞るので、ほかの確認には影響しません。~/.claude/settings.json に足します。
{
"hooks": {
"PermissionRequest": [
{
"matcher": "ExitPlanMode",
"hooks": [
{
"type": "command",
"command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'"
}
]
}
]
}
}
- フックが承認すると、Claude Code はプランモードを抜け、プランモードに入る前の権限モードに戻します。会話の記録には、ダイアログが出るはずの場所に「Allowed by PermissionRequest hook」と出ます。この経路は常に現在の会話を保ち、ダイアログのように文脈を消して新しい実装セッションを始めることはできません
- 特定の権限モードに切り替えるには、フックの出力に
setModeを持つupdatedPermissions配列を入れます。modeはdefault・acceptEdits・bypassPermissionsなどの権限モード、destination: "session"は現在のセッションだけに適用します
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedPermissions": [
{ "type": "setMode", "mode": "acceptEdits", "destination": "session" }
]
}
}
}
補足
bypassPermissions が効くのは、バイパスモードがすでに使える状態で起動したセッションだけです(--dangerously-skip-permissions・--permission-mode bypassPermissions・--allow-dangerously-skip-permissions、またはユーザー設定・--settings・管理設定の permissions.defaultMode: "bypassPermissions")。permissions.disableBypassPermissionsMode でバイパスモードが無効の場合や、制限モードで起動した場合は効きません。Claude Code は、これを defaultMode として保存しません。
注意
matcher はできるだけ狭くします。.* や空の matcher にすると、ファイル書き込みやシェルコマンドを含むすべてのツールの権限確認を自動承認してしまいます。決定のフィールドの全体はフックのリファレンスの PermissionRequest の節にあります。
フックの仕組み#
Claude Code は、ライフサイクルの決まった時点でフックイベントを発火します。イベントが発火すると、一致するフックをすべて並行して実行します。重複したハンドラーの扱いはフックのリファレンスの「Hook handler fields」の節にあります。イベントの全一覧と発火のタイミングはフックのリファレンスにまとめてあります。
各フックには type があり、実行のしかたが決まります。多くは、シェルコマンドを実行する "type": "command" です。ほかに4種類あります。
"type": "http":イベントのデータを URL へ POST する(後述「HTTP フック」)"type": "mcp_tool":設定済みの MCP サーバーのツールを呼ぶ"type": "prompt":LLM に1回だけ評価させる(後述「プロンプト型フック」)"type": "agent":ツールを使える複数ターンの検証(後述「エージェント型フック」。実験的で、変更されうる)
複数のフックの結果をまとめる#
同じイベントに複数のフックが一致すると、Claude Code が結果を統合する前に、各フックのコマンドがすべて最後まで走ります。1つのフックが deny を返しても、並ぶフックの実行は止まりません。別のフックの副作用を、1つのフックの deny で抑えられるとは考えないでください。
一致したフックがすべて終わると、Claude Code は出力を結合します。PreToolUse の権限の判定では、最も厳しい答えが適用され、順は deny・defer・ask・allow です。additionalContext のテキストは、すべてのフックのものが残り、まとめて Claude に渡されます。
次の例は、Bash に2つの PreToolUse フックを登録します。1つ目は、すべてのコマンドをログファイルに追記して 0 で終わります。2つ目は、コマンドに rm -rf が含まれていれば、終了コード 2 で拒否するスクリプトを動かします。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r .tool_input.command >> ~/.claude/bash.log"
},
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm-rf.sh"
}
]
}
]
}
}
Claude が rm -rf /tmp/build を実行しようとすると、2つのフックが並行して動きます。ログのフックはコマンドを ~/.claude/bash.log に書いて 0 で終わり、判定を出しません。ガードレールのフックは 2 で終わり、ツール呼び出しを拒否します。拒否が優先されるので、Claude Code はコマンドをブロックし、ガードレールの stderr を Claude に見せます。ログのフックはすでに動いているので、ログの記録は残ります。
入力を読んで出力を返す#
フックは、stdin・stdout・stderr・終了コードで Claude Code とやり取りします。イベントが発火すると、Claude Code はそのイベント固有のデータを JSON でスクリプトの stdin に渡します。スクリプトはそれを読んで処理し、終了コードで次にすることを Claude Code に伝えます。
フックの入力#
どのイベントにも、セッションの一意の ID の session_id と、イベント発火時の作業ディレクトリの cwd などの共通フィールドがあり、イベントの種類ごとに別のデータが加わります。Claude が Bash コマンドを実行するとき、PreToolUse フックは stdin で次のフィールドを受け取ります。
hook_event_name:フックを起動したイベントtool_name:Claude が使おうとしているツールtool_input:Claude がツールに渡した引数。Bash では、commandフィールドにシェルコマンドが入る
npm test コマンドのフックの入力は次のようになります。
{
"session_id": "abc123",
"cwd": "/Users/sarah/myproject",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}
UserPromptSubmit フックは代わりに prompt のテキストを受け取り、SessionStart フックは startup・resume・clear・compact・fork のいずれかの source を受け取ります。共通フィールドとイベント固有のスキーマはフックのリファレンスにあります。
フックの出力#
スクリプトは、stdout か stderr に書き、特定の終了コードで終わって、Claude Code に次にすることを伝えます。次の PreToolUse フックは、コマンドをブロックします。
#!/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 # stderr becomes Claude's feedback
exit 2 # exit 2 = block the action
fi
exit 0 # exit 0 = no decision; the normal permission flow applies
終了コードで、次に起きることが決まります。
- 終了コード 0:終了コードの上では、フックは異議を唱えない
PreToolUseフックでは、これはツール呼び出しを承認するものではなく、通常の権限の流れが引き続き適用されるUserPromptSubmit・UserPromptExpansion・SessionStart・PostModelSwitchのフックでは、Claude Code がプレーンテキストとして扱う stdout が、Claude の文脈に加わる
- 終了コード 2:Claude Code はその操作をブロックする。理由を stderr に書く。どこに届くかはイベントで変わる:Claude へのフィードバックとして渡して方針を変えさせるもの、ユーザーに見せるもの、
ConfigChangeやElicitationのようにメッセージを出さないものがある。SessionStartなど、ブロックできないイベントもあり、そこでは、終了コード 2 は stderr をユーザーに見せ、実行は続く(イベントごとの一覧はフックのリファレンス) - それ以外の終了コード:多くのイベントでは、stdout に何を出したかで結果が決まる
- スキーマ検証を通る、解析されたオブジェクト:終了コードは無視され、JSON だけが結果を決め、フックはエラーとして報告されない。
WorktreeCreateがどんな非 0 でも失敗するなどのイベントごとの例外は、リファレンスの終了コードの出力の節にある - スキーマ検証を通らない、解析されたオブジェクト、または JSON として解析しようとしたが有効な JSON でない stdout:ブロックしないエラー。通知に検証か解析のメッセージが付く
- プレーンテキストとして扱われる stdout、または空の stdout:ブロックしないエラーとして、操作は進む。記録に
<hook name> hook errorの通知が出て、続けて stderr の最初の1行がFailed with non-blocking status code:を前に付けて出る。stderr の全文を得るには、claude --debugか、セッション中に/debugを実行して、デバッグログを有効にする
- スキーマ検証を通る、解析されたオブジェクト:終了コードは無視され、JSON だけが結果を決め、フックはエラーとして報告されない。
構造化された JSON 出力#
終了コードでできるのは、ブロックするか黙るかだけです。より細かく制御するには、0 で終了して JSON オブジェクトを stdout に出します。
補足
stderr のメッセージつきでブロックするなら終了コード 2、構造化された制御なら終了コード 0 と JSON、のどちらかをフックごとに選びます。混ぜたときにどうなるかは、リファレンスの終了コードの出力の節にあります。
たとえば PreToolUse フックは、ツール呼び出しを拒否して Claude に理由を伝えたり、ユーザーの承認へ回したりできます。
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Use rg instead of grep for better performance"
}
}
"deny" なら、Claude Code はツール呼び出しを取り消し、permissionDecisionReason を Claude に返します。PreToolUse では、permissionDecision の値ごとに次のように扱われます。
"allow":対話式の権限確認を省く。拒否ルールと確認(ask)ルール(エンタープライズ管理の拒否リストを含む)、requiresUserInteractionの印がある MCP ツール、その設定が Claude Code に届くセッションで組織がaskにしたコネクタのツールの確認は、引き続き適用される"deny":ツール呼び出しを取り消し、理由を Claude に送る"ask":ユーザーに通常どおり権限確認を出す
4つ目の値 "defer" は、-p フラグの非対話モードで使えます。ツール呼び出しを保ったままプロセスを終了し、Agent SDK のラッパーが入力を集めて再開できるようにします(フックのリファレンス参照)。
PreModelSwitch フックも同じ permissionDecision を返します:"allow" はモデルの切り替えを進め、"deny" は取り消します。"ask" は、対話セッションで /model を実行したときは切り替えの確認を出し、それ以外では Claude Code が拒否として扱います。
イベントによって、判定のパターンは違います。たとえば PostToolUse と Stop のフックは最上位の decision: "block" を使い、PermissionRequest は hookSpecificOutput.decision.behavior を使います。イベントごとの一覧はフックのリファレンスの決定制御の表にあります。
UserPromptSubmit フックでは、Claude の文脈に文章を入れるのに hookSpecificOutput.additionalContext を使います。additionalContext は hookSpecificOutput の中に入れます。JSON の最上位に置くと、Claude Code は黙って無視します。次の出力は、すべてのプロンプトに現在のブランチの状態を足します。
{
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "Current branch: release-42. Deploy freeze until Friday."
}
}
type: "prompt" のフックは出力の扱いが違います(後述「プロンプト型フック」)。
matcher で絞る#
matcher が無いと、フックはそのイベントの発生のたびに発火します。matcher で絞り込めます。たとえば、フォーマッターをファイル編集の後だけに動かし、すべてのツール呼び出しの後には動かさないなら、PostToolUse フックに matcher を足します。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "prettier --write ..." }
]
}
]
}
}
"Edit|Write" の matcher は、Claude が Edit か Write を使うときだけ発火し、Bash・Read などほかのツールでは発火しません。カンマも同じように選択肢を区切るので、"Edit, Write" は同じ意味です。名前そのままや正規表現の評価のしかたは、フックのリファレンスの matcher の節にあります。
補足
Claude は、シェルコマンドの実行でもファイルを作ったり変更したりできます。コンプライアンスの走査や監査ログのように、すべてのファイルの変更を見る必要があるフックには、作業ツリーをターンごとに1回走査する Stop フックを足します。呼び出しごとに押さえるなら、Bash|PowerShell にも一致させ、スクリプトで git status --porcelain から変更・未追跡のファイルを列挙します。ディスク上で特定のファイルが変わったとき、誰が書いたかにかかわらずフックを動かすなら、FileChanged フックを使います。
イベントごとに、matcher が見るフィールドは決まっています。全イベントの matcher の対象と値はフックのリファレンスの表にあります。matcher の例をさらに3つ示します。
すべての Bash コマンドを記録する:Bash のツール呼び出しだけに一致させ、各コマンドをファイルに記録します。PostToolUse はコマンドが完了した後に発火するので、tool_input.command に実行した内容が入ります。jq -r '.tool_input.command' でコマンドの文字列だけを取り出し、>> でログファイルに追記します。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt"
}
]
}
]
}
}
MCP ツールに一致させる:MCP ツールは組み込みツールと違う命名で、mcp__<server>__<tool> です(例:mcp__github__search_repositories、mcp__filesystem__read_file)。プラグインに同梱されたサーバーのツールは、スコープ付きのサーバー部分を使い、mcp__plugin_my-plugin_db__query のようになります。特定のサーバーの全ツールを狙うには正規表現の matcher を使い、サーバーをまたぐなら mcp__.*__write.* のようなパターンを使います。次のコマンドは、フックの JSON 入力から jq でツール名を取り出し、stderr に書きます。stderr に書くと、stdout が JSON 出力のために空いたままになり、メッセージはデバッグログに送られます。
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__github__.*",
"hooks": [
{
"type": "command",
"command": "echo \"GitHub tool called: $(jq -r '.tool_name')\" >&2"
}
]
}
]
}
}
セッション終了時に片付ける:SessionEnd イベントは、セッションが終わった理由で matcher を使えます。次のフックは、/clear を実行したときの理由 clear でだけ発火し、通常の終了では発火しません。
{
"hooks": {
"SessionEnd": [
{
"matcher": "clear",
"hooks": [
{
"type": "command",
"command": "rm -f /tmp/claude-scratch-*.txt"
}
]
}
]
}
}
if フィールドでツール名と引数を絞る#
if フィールドは、権限ルールの構文で、ツール名と引数をあわせてフックを絞ります。ツール呼び出しが一致したときだけ、フックのプロセスが起動します。グループ単位でツール名だけを見る matcher より細かい絞り込みです。たとえば次の設定は、Bash コマンドすべてではなく、git コマンドのときだけフックを動かします。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(git *)",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-git-policy.sh"
}
]
}
]
}
}
フックのコマンドが動くかは、if のパターンの形と、Claude が呼ぶ Bash コマンドで決まります。
if のパターン |
Bash コマンド | フックが動くか | 理由 |
|---|---|---|---|
Bash(git *) |
git push |
動く | コマンド名が一致 |
Bash(git *) |
npm test && git push |
動く | 各サブコマンドを調べ、git push が一致 |
Bash(git *) |
echo $(git log) |
動く | $() とバッククォートの中のコマンドも調べ、git log が一致 |
Bash(git *) |
echo $(date) |
動かない | git * に一致するサブコマンドが無い |
Bash(git push *) |
echo $(date) |
動く | コマンド名以上を指定するパターンは、$()・バッククォート・$VAR があるとフックを動かす |
- Bash の入力がどのコマンドを実行するか Claude Code が判定できないときは、パターンに関係なくフックを動かします。サブコマンド単位で絞れる形と絞れない形は、フックのリファレンスの Bash の一致の表にあります。この絞り込みは最善努力なので、許可や拒否を確実に強制するにはフックでなく権限を使います
ifは、権限ルールと同じパターン("Bash(git *)"・"Edit(*.ts)"など)を受け付けます。複数のツール名に一致させるには、それぞれifを持つ別々のハンドラーにするか、パイプで選択肢を書けるmatcherで絞りますifが使えるのは、ツールのイベント(PreToolUse・PostToolUse・PostToolUseFailure・PermissionRequest・PermissionDenied)だけです。それ以外のイベントに足すと、フックが動かなくなります
フックの置き場所#
足す場所で、適用範囲が決まります。
| 場所 | 範囲 | 共有できるか |
|---|---|---|
~/.claude/settings.json |
自分の全プロジェクト | いいえ(自分のマシンだけ) |
.claude/settings.json |
1つのプロジェクト | はい(リポジトリにコミットできる) |
.claude/settings.local.json |
1つのプロジェクト | いいえ(Claude Code が設定を保存するときに gitignore される) |
| 管理ポリシー設定 | 組織全体 | はい(管理者が制御) |
プラグインの hooks/hooks.json |
プラグインが有効な間 | はい(プラグインに同梱) |
| スキルの frontmatter | スキルを呼んだ後のセッションの残り | はい(スキルのファイルに定義) |
| サブエージェントの frontmatter | そのサブエージェントが動いている間 | はい(サブエージェントのファイルに定義) |
- Claude Code で
/hooksを実行すると、設定済みのフックを、イベントごとにまとめて見られます - フックを無効にするには、設定ファイルに
"disableAllHooks": trueを書きます。Claude Code は、設定の優先順位を適用した後に残る値を読むので、プロジェクトの設定ファイルがあなたの設定を上書きすることがあります。管理設定のフックは、そこでもdisableAllHooksを設定していない限り、動き続けます - Claude Code が動いている間に設定ファイルを直接編集しても、通常は、ファイルの監視がフックの変更を自動で拾います
プロンプト型フック#
決定的な規則でなく判断が要る場面では、type: "prompt" のフックを使います。シェルコマンドの代わりに、Claude Code が、あなたのプロンプトとフックの入力データを Claude のモデルに送って判断させます。より高い能力が要るなら、model フィールドで別のモデルを指定できます。
モデルの役目は、判断を JSON で返すことだけです。
"ok": true:操作が進む"ok": false:イベントによって動作が変わるStopとSubagentStop:reasonが Claude に返され、作業を続ける。ただし、応答が"impossible": trueも設定して、その条件が決して満たされないと示す場合は、Claude Code が停止を許し、ターンが終わるPreToolUse:ツール呼び出しは拒否される。既定ではターンが終わり、拒否のreasonが警告行としてチャットに出る。フックにcontinueOnBlock: trueを付けると、代わりにreasonをツールエラーとして Claude に返し、Claude が調整して続けられる。v2.1.210 より前は、拒否のreasonがツールエラーとして Claude に返り、ターンが続いたPostToolUse:既定ではターンが終わり、reasonが警告行としてチャットに出る。continueOnBlock: trueなら、reasonを Claude に返してターンを続けるPostToolBatch・UserPromptSubmit・UserPromptExpansion:ターンが終わり、reasonが警告行としてチャットに出る
次の例は、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\"}."
}
]
}
]
}
}
設定項目の全体はフックのリファレンスのプロンプト型フックの節にあります。
エージェント型フック#
注意
エージェント型フックは実験的です。動作と設定は将来のリリースで変わることがあります。本番のワークフローでは、コマンド型フックを選んでください。
検証でファイルを調べたりコマンドを実行したりする必要があるときは、type: "agent" のフックを使います。LLM を1回だけ呼ぶプロンプト型と違い、エージェント型フックはサブエージェントを起動し、ファイルを読み、コードを検索し、ほかのツールを使って条件を確かめてから判断を返します。
- 応答の形式はプロンプト型と同じ
"ok"/"reason"で、既定のタイムアウトはより長い 60 秒、ツールを使うターンは最大 50 回 - プロンプト型の
impossibleフィールドには対応しない ok: falseのとき、Claude Code は、同じイベントでcontinueOnBlock: trueを付けたプロンプト型フックと同じように扱う。つまりPreToolUseとPostToolUseではターンが続く。エージェント型フックにcontinueOnBlockフィールドは無い$ARGUMENTSのプレースホルダーは、Claude Code がフックの JSON 入力に置き換える。フィールドはフックのリファレンスのエージェント型フックの設定の節にある
次の例は、Claude が止まる前に、テストが通ることを確かめます。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
"timeout": 120
}
]
}
]
}
}
フックの入力データだけで判断できるときはプロンプト型を、コードベースの実際の状態に照らして確かめる必要があるときはエージェント型を使います。
HTTP フック#
シェルコマンドを動かす代わりに、イベントのデータを HTTP エンドポイントへ POST するには、type: "http" のフックを使います。エンドポイントは、コマンド型フックが stdin で受け取るのと同じ JSON を受け取り、結果は同じ JSON 形式で HTTP レスポンスボディに返します。Web サーバー・クラウド関数・外部サービスにフックの処理を任せたいとき、たとえばチーム全体のツール使用のイベントを記録する共有の監査サービスなどに向きます。
次の例は、すべてのツール使用を、ローカルのログ収集サービスに POST します。
{
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/tool-use",
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
}
- エンドポイントは、コマンド型フックと同じ出力形式の JSON をレスポンスボディに返す。ツール呼び出しをブロックするには、適切な
hookSpecificOutputのフィールドを持つ 2xx のレスポンスを返す。HTTP ステータスコードだけでは操作をブロックできない - ヘッダーの値は、
$VAR_NAMEか${VAR_NAME}の形で環境変数を埋め込める。解決されるのはallowedEnvVarsの配列に挙げた変数だけで、ほかの$VARは空のままになる - 設定項目とレスポンスの扱いは、フックのリファレンスの HTTP フックの節にある
制約とトラブルシューティング#
制約#
フックを設計するときは、次の制約を押さえておきます。
- コマンド型フックは、stdout・stderr・終了コードだけでやり取りする。
/コマンドやツール呼び出しは起動できない。additionalContextで返したテキストは、Claude がプレーンテキストとして読むシステムリマインダーとして注入される。HTTP フックは代わりにレスポンスボディでやり取りする - フックのタイムアウトは、種類で違う。フックごとに
timeoutフィールド(秒)で上書きできるcommand・http・mcp_tool:10 分。Claude Code は、UserPromptSubmit・PreModelSwitch・PostModelSwitchのフックでは既定を 30 秒に、MessageDisplayでは 10 秒に下げるprompt:30 秒agent:60 秒- どの種類の
SessionEndフックも、合計 1.5 秒の予算を共有する。設定がそれより長い、フックごとのtimeoutを指定していれば、Claude Code は予算を合わせて引き上げ、上限は 60 秒
PostToolUseフックは、ツールがすでに実行されているので、操作を取り消せないPermissionRequestフックは、Claude Code があなたに権限を尋ねようとするとき、または確認を出せないため自動で拒否するはずの呼び出しのときに発火する。-pフラグの非対話モードでは、dontAskモードの外では引き続き動き、どのフックも決めず、ほかに答えるものもない呼び出しは拒否されるStopフックは、タスクの完了時だけでなく、Claude が応答を終えるたびに発火する。ユーザーの割り込みでは発火せず、API エラーでは代わりにStopFailureが発火する- 複数の
PreToolUseフックがupdatedInputでツールの引数を書き換えると、最後に終わったものが有効になる。フックは並行して動くので、順序は決まらない。同じツールの入力を複数のフックが書き換えないようにする
フックと権限モード#
PreToolUse フックは、dontAsk を含むどの権限モードでも、権限モードの確認より前に発火します。permissionDecision: "deny" を返すフックは、bypassPermissions モードや --dangerously-skip-permissions でもツールをブロックします。ユーザーが権限モードを変えても回避できない方針を強制できます(権限モードは権限モード参照)。
逆は成り立ちません。"allow" を返すフックは、設定の拒否ルールを迂回せず、requiresUserInteraction の印がある MCP ツールや、組織が ask にしたコネクタのツール(その設定が Claude Code に届くセッション)の確認も省けません。設定ファイルと、プラグインの hooks/hooks.json のフックは、制限を強められますが、権限ルールが許す範囲を超えて緩められません。
インストールした Mod(モッド) が tool.check を扱うと、PreToolUse フックがブロックした呼び出しを承認できます。ただし、フックが managed settings にあるときを除きます。どのルールが Mod より優先されるかは、権限ルールの「フックで権限を拡張する」にあります。
フックが動かない#
設定してあるのに、実行されないとき。
/hooksを実行して、フックが正しいイベントの下に出ているか確かめる- メニューに
Only hooks from managed settings run hereと出るなら、組織がallowManagedHooksOnlyを設定している。ユーザー・プロジェクト・ローカルの設定ファイルのフックは動かず、一覧にも出ない - matcher がツール名に正確に一致するか確かめる(matcher は大文字小文字を区別する)
- 正しいイベントの種類を起こしているか確かめる:
PreToolUseはツール実行前、PostToolUseは後。PermissionRequestは、権限を尋ねようとするとき発火する(非対話の場合は前述の制約を参照)
出力に「hook error」が出る#
記録に「PreToolUse hook error: ...」のようなメッセージが出るとき。
- スクリプトが意図せず非 0 で終了している。サンプルの JSON をパイプで渡して手で試す
echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh
echo $? # Check the exit code
- 「command not found」と出るなら、絶対パスか
${CLAUDE_PROJECT_DIR}でスクリプトを参照する。シェルのクォートを完全に避けるには、"args": []を足して、シェルを通さずスクリプトを直接起動する exec 形式に切り替える - 「jq: command not found」と出るなら、
jqを入れるか、JSON の解析に Python や Node.js を使う - 通知に JSON の検証メッセージが出るなら、フックの stdout は JSON として解析されたが、スキーマ検証に失敗している。JSON の解析メッセージなら、stdout が JSON オブジェクトのように見えたが、有効な JSON ではなかった。どちらも終了コード 0 でも起きる
- 解析の失敗を直すには、文字列の連結ではなく
jqなどの JSON エンコーダーでペイロードを作り、値の中の引用符とバックスラッシュがエスケープされるようにする。終了コードと JSON の組み合わせは、リファレンスの終了コードの出力の節にある
- 解析の失敗を直すには、文字列の連結ではなく
- スクリプトがまったく動かないなら、実行権限を付ける:
chmod +x ./my-hook.sh
/hooks に設定したフックが出ない#
設定ファイルを編集したのに、メニューに出ないとき。
- ファイルの編集は、通常、自動で拾われる。数秒たっても出なければ、ファイルの監視が変更を見逃した可能性があるので、セッションを再起動して読み込み直す
- JSON が有効か確かめる(末尾のカンマとコメントは使えない)
- 設定ファイルが正しい場所にあるか確かめる:プロジェクトのフックは
.claude/settings.json、全体のフックは~/.claude/settings.json
Stop フックがブロックの上限に達する#
Claude が止まらずに作業を続け、そのうち、Stop フックが連続でブロックしすぎたという警告つきでターンを終えるとき。
Claude Code は、Stop フックが Claude からのツール呼び出しなしに8回続けてブロックすると、そのフックを上書きします。フックのスクリプトは、すでに継続を起こしたかを確かめる必要があります。JSON 入力の stop_hook_active フィールドを解析し、true なら早く終えます。
#!/bin/bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # Allow Claude to stop
fi
# ... rest of your hook logic
フックが正当に8回を超える反復を必要とするなら、CLAUDE_CODE_STOP_HOOK_BLOCK_CAP で上限を上げます。
フックの JSON が効かない#
フックが有効な JSON を出しているのに、判定が効かず、記録にもエラーが出ないとき。当てはまる原因を確かめます。
- JSON の前に余分な出力がある:何かが先に stdout に書いている(通常は、シェルのプロファイルの無条件の
echo)。出力が{で始まらなくなり、Claude Code は JSON として解析しない - フィールドの階層が違う:各フィールドの位置を、フックのリファレンスの JSON 出力の形式と比べる。たとえば
permissionDecisionは、最上位でなくhookSpecificOutputの中に置く
args を持たないシェル形式のコマンド型フックを動かすとき、Claude Code は macOS と Linux では sh -c、Windows では Git Bash(Git Bash が入っていなければ PowerShell)を起動します。このシェルは非対話ですが、Git Bash や、BASH_ENV が ~/.bashrc を指す設定などでは、プロファイルが読み込まれます。そのプロファイルに無条件の echo があると、その出力がフックの JSON の前に付きます。
Shell ready on arm64
{"decision": "block", "reason": "Not allowed"}
結合した出力が { で始まらないので、Claude Code は stdout 全体をプレーンテキストとして扱い、JSON を無視します。終了コード 0 では記録に何も報告されず、解析の試行はデバッグログにだけ残ります。直すには、シェルのプロファイルの echo を、対話シェルでだけ動くように包みます。
# In ~/.zshrc or ~/.bashrc
if [[ $- == *i* ]]; then
echo "Shell ready"
fi
$- はシェルのフラグを持つ変数で、i は対話シェルを表します。フックは非対話シェルで動くので、echo は飛ばされます。
フックが permissionDecision や additionalContext を hookSpecificOutput の中でなく最上位に返すと、JSON は解析されますが、Claude Code は位置の誤ったフィールドをエラーなしで無視します。どのフィールドが無視されたかは、claude --debug で起動し、デバッグログで Hook JSON output had unrecognized keys を検索すると分かります。
デバッグ#
Ctrl+O で記録の画面を開くと、フックの実行結果を確かめられます。
- 成功した実行:フックの JSON が
systemMessageや Stop フックのフィードバックなどを出さない限り、何も見えない。動いたかを確かめるには、整形されたファイルのような効果を確認するか、後述のデバッグログをオンにして、フックをもう一度起こす - ブロックのエラー:多くのイベントでは、フックのフィードバックが見える。フックの JSON がブロックの判定をした場合は、その判定の理由が、そうでなければフックの stderr が出る。
ConfigChangeやElicitationなど、ブロックしてもメッセージを出さないイベントもある - ブロックしないエラー:操作は進み、
<hook name> hook errorの通知と短い説明(Failed with non-blocking status code:を前に付けた stderr の最初の1行、または JSON の検証・解析のメッセージ)が出る
どの終了コードと JSON の組み合わせがどの結果になるか(イベントごとの例外を含む)は、リファレンスの終了コードの出力の節にあります。
どのフックが一致したか、終了コード、stdout、stderr を含む実行の詳細は、デバッグログに出ます。決まったパスに書き出すには claude --debug-file /tmp/claude.log で起動し、別のターミナルで tail -f /tmp/claude.log を実行します。このフラグ無しで始めたなら、セッション中に /debug を実行するとログが有効になり、ログのパスが分かります。
関連#
- 全イベントのスキーマ・JSON 出力形式・非同期フック・MCP ツールのフックはフックのリファレンス
- 共有環境や本番へフックを入れる前に、リファレンスのセキュリティの節を確認する
- Claude に追加の指示や実行できる手順を渡すならスキル、隔離した文脈で作業を回すならサブエージェント、拡張をまとめて配るならプラグイン
公式ドキュメント(英語)
2026年10月5日時点の内容をもとに、日本語でまとめています。