PostToolUse hookとは: ツール実行後の自動処理を設定する
PostToolUse hookは、Claude Codeのツールが成功した直後に動くフックです。設定手順、入力JSON、updatedToolOutputの使い方とつまずきを解説します。
PostToolUse hookとは何か
PostToolUse hookは、Claude Codeがツールを実行して成功したあとに動くフックです。設定はsettings.jsonのhooks.PostToolUseにマッチャーとハンドラーを書くだけで、入力JSONのtool_responseから結果を読めます。ファイルを書き換えたら自動でフォーマットをかける、コマンドの実行結果をログに残す、書き込み内容を外部ツールでチェックする。こうした後処理を毎回手作業で挟まずに済ませるための仕組みです。
似た名前のPreToolUse hookとは役割が逆です。この非対称性を最初に押さえておくと、設定を書くときの迷いが減ります。
呼ばれるタイミングでできることが変わる
PreToolUse
ツールが動く前に呼ばれ、実行そのものを止められます。入力に結果(tool_response)はまだありません。
PostToolUse
ツールが成功したあとに呼ばれます。書き込みやコマンドの実行は完了済みで、止められません。できるのは後始末とClaudeへのフィードバックです。
前提: hooksの設定階層
PostToolUse hookは.claude/settings.json(プロジェクト単位)、~/.claude/settings.json(全プロジェクト共通)、プラグインのhooks/hooks.json、スキルやサブエージェントのfrontmatterのいずれかに定義します。設定は3段階の入れ子です。
設定を書く順番
- 1
イベントを選ぶ
PostToolUseをhooksの下に置きます。 - 2
マッチャーで対象ツールを絞る
Bash、Edit|Writeのようにツール名で絞ります。 - 3
ハンドラーを書く
シェルコマンド・HTTPエンドポイント・MCPツール・プロンプト・エージェントのいずれかを指定します。
マッチャーはツール名に対して評価されます。英数字・アンダースコア・ハイフン・スペース・カンマ・|だけで構成された文字列は完全一致(または|区切りの複数一致)として扱われ、それ以外の文字を含むと正規表現として評価されます。Edit|WriteはEditとWriteの両方に一致し、mcp__memory__.*はmemoryサーバーの全ツールに一致します。マッチャーを省略するか"*"にすると、ツールの種類を問わずすべてのPostToolUse呼び出しで発火します。
PostToolUseが受け取る入力
PostToolUse hookには、他のツールイベントと同じtool_name・tool_inputに加えて、ツールが実際に返した結果であるtool_responseが渡されます。このtool_responseの有無がPreToolUseとの決定的な違いです。
# stdin から JSON が渡ってくる。jq で必要なフィールドだけ取り出す
cat <<'JSON' | jq '.tool_name, .tool_response'
{
"session_id": "abc123",
"cwd": "/Users/you/project",
"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" },
"duration_ms": 12
}
JSONduration_msはツールの実行時間(ミリ秒)で、パーミッションプロンプトやPreToolUse hookの待ち時間は含みません。ファイル系ツールのtool_inputのパスは常に絶対パスで、区切り文字はOSのネイティブな形式(Windowsではバックスラッシュ)になります。
hookスクリプトは、Claude Codeを起動しなくても標準入力にJSONを流せば単体で動かせます。v2.1.289で標準入力を読んでadditionalContextを返すスクリプトにJSONを流すと、次の出力になります。
echo '{"hook_event_name":"PostToolUse","tool_name":"Write","tool_input":{"file_path":"/tmp/x/app.ts"},"duration_ms":12}' | ./guard.sh
# {"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"format ok: /tmp/x/app.ts"}}
echo '{"hook_event_name":"PostToolUse","tool_name":"Write","tool_input":{"file_path":"/tmp/x/api.generated.ts"}}' | ./guard.sh
# {"decision":"block","reason":"自動生成ファイルです。schema.ts を直してください"}2つ目の出力でdecisionがhookSpecificOutputの外にある点に注意してください。decisionとreasonはJSONの最上位、additionalContextやupdatedToolOutputはhookSpecificOutputの中に置きます。
設定手順: matcherとhandlerを書く
最小構成は、対象ツールを絞ったマッチャーの下にコマンドハンドラーを1つ置くだけです。次の例は、EditまたはWriteが成功するたびにリンタースクリプトを走らせます。
mkdir -p .claude/hooks
cat > .claude/hooks/lint-check.sh <<'SH'
#!/bin/bash
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')
[ -z "$file_path" ] && exit 0
npx eslint "$file_path" --fix
SH
chmod +x .claude/hooks/lint-check.sh{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/lint-check.sh",
"args": []
}
]
}
]
}
}${CLAUDE_PROJECT_DIR}はセッション開始時のプロジェクトルートを指すプレースホルダーで、Claude Codeがワークツリーに入っても値は変わりません。パスを含むハンドラーでは、シェルのクォート崩れを避けるためにargsを使ったexec形式(シェルを介さず直接実行)にするのが安全です。
実行結果を後処理する3パターン
PostToolUseの代表的な使い道は、フォーマット・記録・チェックの3つです。
フォーマット — 書き込み直後に整形する
上の例のように、Edit|Writeにマッチさせてフォーマッタやリンターを自動実行します。ツールの出力はすでに確定しているので、フォーマットの結果をClaudeに伝えたいときは後述のadditionalContextを使います。ESLintやruffなど言語別の自動修正コマンドと、直せない指摘が残ったときの終了コード設計はPostToolUse hookのLint自動修正にまとめています。
記録 — 変更履歴を外部に残す
ツール名を絞らずにmatcher: "*"で発火させ、git status --porcelainのようなコマンドで変更点を自分で調べてログに追記する方法です。公式ドキュメントも、ツール名で絞れないときはmatcherを省略するか"*"にし、ハンドラー側でgit status --porcelainなどから変更を検出する方法を示しています。git diffは追跡外ファイルを拾いませんが、git status --porcelainなら拾えます。
追跡中のa.txtだけを編集し、新規のb.txtはgit addしていないリポジトリでgit status --porcelainを実行すると、次のように出ます。
M a.txt
?? b.txtgit diff --name-onlyが拾うのはa.txtだけなので、新規ファイルの記録漏れはこの差で起きます。Bashコマンドが変えたファイルについては、条件を満たすとtool_response.bashEditDiffに一覧が入ります(v2.1.269以降、変更を記録する条件はbashEditDiffEnabledの設定とモードで決まります。公開ベータの機能で、ポリシー強制には使わない前提です)。
cat > .claude/hooks/audit-log.sh <<'SH'
#!/bin/bash
input=$(cat)
tool=$(echo "$input" | jq -r '.tool_name')
ts=$(date -u +%FT%TZ)
echo "$ts $tool" >> ~/.claude/audit.log
git status --porcelain >> ~/.claude/audit.log
SH
chmod +x .claude/hooks/audit-log.shチェック — 書き込み内容を検証してClaudeへ返す
MCPサーバーにセキュリティスキャンツールがある場合、type: "mcp_tool"ハンドラーで直接呼び出せます。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "mcp_tool",
"server": "my_server",
"tool": "security_scan",
"input": { "file_path": "${tool_input.file_path}" }
}
]
}
]
}
}チェックで問題を見つけた場合、ツールの実行自体はすでに終わっているため差し戻しはできません。次の「Claudeへのフィードバック」で扱うdecision: "block"やadditionalContextで、Claudeに次の一手を促す形になります。
Claudeへのフィードバック方法
PostToolUse hookは、実行済みのツール呼び出しに対して終了コードかJSON出力でフィードバックを返せます。JSONのうちdecisionとreasonは最上位、残りはhookSpecificOutputの中に置きます。
| フィールド | 置き場所 | できること |
|---|---|---|
decision: "block" + reason | 置き場所最上位 | できることツール結果の横に理由を添える(結果そのものは変わらず、Claudeは元の出力も見られる) |
additionalContext | 置き場所hookSpecificOutput | できることツール結果の横にシステムリマインダーとして差し込む。チャットには表示されず、Claudeは次のモデルリクエストで読む |
updatedToolOutput | 置き場所hookSpecificOutput | できることClaudeに見せるツール出力をまるごと差し替える |
classifierContext | 置き場所hookSpecificOutput | できることAuto modeの分類器だけに向けた短いメモ(Claude Code v2.1.236以降) |
| 終了コード2 | 置き場所なし(stderr) | できることブロックはできず、stderrがClaudeに見える形で渡る。警告を気づかせたいだけなら手早い手段 |
注入するテキストが10,000文字を超えると、Claude Codeはセッションディレクトリのファイルに書き出し、ファイルパスと先頭2,000文字までのプレビューだけをClaudeに渡します。大きなログを丸ごと返すより、要点に絞って返す設計が向きます。
標準出力(stdout)は{で始まり}で終わるときだけJSONとして解釈され、それ以外はプレーンテキスト扱いです。終了コード0で成功を返し、JSONで構造化した制御を行うのが基本形です。
stderrは終了コード0のままだとデバッグログにしか残らず、Claudeには見えません。プロンプトフックやエージェントフックがok: falseを返したときは、既定ではターンが終わって理由が警告行として表示されます。プロンプトフックにcontinueOnBlock: trueを付けると理由がClaudeに戻って続行します。エージェントフックにはcontinueOnBlockの欄がなく、この続行側の挙動になります。
updatedToolOutputの書き方
Claudeに見せるツール出力を差し替えるときは、hookSpecificOutputの中にupdatedToolOutputを置きます。次の例はBashの出力を伏せ字に置き換えます。
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"updatedToolOutput": {
"stdout": "[redacted]",
"stderr": "",
"interrupted": false,
"isImage": false
}
}
}値はツールの出力と同じ形にします。組み込みツールで形が合わないと無視され、元の出力が使われます。MCPツールだけを対象にするupdatedMCPToolOutputもありますが、全ツールで使えるupdatedToolOutputが推奨されています。
Auto modeの分類器向けに使うclassifierContextは、そのクエリがステージング環境に向けて実行されたことを伝える、といった短い事実のメモに限定します。1回のツール呼び出しにつき2,000文字が上限で、設定ファイル・プラグイン・スキル・サブエージェントのfrontmatterから来たメモは「未検証のアプリケーション提供コンテキスト」として扱われ、ユーザーの同意を装う内容は会話本文と突き合わせて確認されます。
PostToolUseとほかのフックの使い分け
| 目的 | 使うフック | 理由 |
|---|---|---|
| ツール実行を止めたい | 使うフックPreToolUse | 理由実行前にしか防げない |
| 成功後に整形・記録・チェックしたい | 使うフックPostToolUse | 理由実行後にしかtool_responseを受け取れない |
| ツールが失敗したときだけ動かしたい | 使うフックPostToolUseFailure | 理由PostToolUseは成功時のみ発火する |
| 並列実行したツールの結果をまとめて処理したい | 使うフックPostToolBatch | 理由個々のPostToolUseとは別に、バッチ単位で発火する |
| ファイルへの書き込み者を問わず変更を検知したい | 使うフックFileChanged | 理由Bashコマンドや外部プロセスによる書き換えも拾える |
Claude CodeのEditやWriteで書き換えたファイルは、PostToolUseが拾います。ただしEdit|WriteにマッチさせたPostToolUseは、Bashコマンドや外部プロセスによる書き換えでは発火しません。Bashツール経由なら"*"のPostToolUseで拾えます。Claude Code外のプロセスによる書き換えは、FileChangedでないと拾えません。監視漏れを防ぎたいなら、ツール経由の変更用にPostToolUse、それ以外の経路も含めた監視用にFileChangedを併用します。FileChangedはmatcherに監視するファイル名を指定する点がPostToolUseと違います。pandasの集計・結合スクリプトの実行結果を自動チェックする例はClaude Codeでpandasのデータ処理を書く実践手順にまとめています。
PostToolUseFailure専用の設定手順や、ツール失敗時だけ動くリカバリの書き方はPostToolUseFailure hookでツール失敗時だけ動くリカバリを書くにまとめています。並列実行したツール結果をまとめて処理したい場合はPostToolBatch hookで並列ツール結果をまとめて処理する、承認ダイアログの手前で判定を挟みたい場合はPermissionRequest hookで承認ダイアログの前に判定を挟むが参考になります。
よくあるつまずき
ツールの実行を止められると誤解する
終了コード2を返してもツール呼び出しはブロックされません。危険なコマンドを未然に防ぎたいなら、PreToolUseのpermissionDecision: "deny"を使います。
stdoutに余計な文字列が混ざりJSONとして解釈されない
シェルのプロファイルがログイン時に何か出力する環境だと、その文字列がJSON出力の前後に混入し、パースに失敗することがあります。標準出力にはJSONオブジェクトだけを出す設計にし、ログはファイルやstderrに書くようにします。
hookが動いたのか分からない
claude --debug-file <path>で起動すると、フックの実行がそのファイルに書かれます。--debugだけではターミナルに出ず、~/.claude/debug/<session-id>.txtに書かれます。Writeにマッチさせた、hook-ranと出力するだけのPostToolUseフックなら、Hook PostToolUse:Write (PostToolUse) success:に続けて出力が残ります。出力が{で始まらないとtreating as plain textと記録されるので、JSONを返したつもりのフックが効かないときの切り分けにも使えます。
.cmdや.batのシムをexec形式で直接呼んで失敗する
Windowsでnpmやnpxがインストールする.cmd・.batのシムは実行ファイルではないため、argsを指定したexec形式では起動できません。nodeコマンドと本体スクリプトのパスを直接指定するか、argsを省いたシェル形式で呼び出します。
MCPツールハンドラーがセッション開始直後だけ動かない
type: "mcp_tool"のハンドラーは、Claude CodeがセッションのMCPサーバーをフックから使える状態にしたあとでないと呼び出せません。SessionStartやSetupは接続前に発火するため、そこにmcp_toolハンドラーを置いてもエラーを出さずにスキップされます(デバッグログには記録されます)。PostToolUseはツール呼び出しの後に発火するため、この制約に当たることは通常ありません。
まとめ
Claudeに見せる結果を変えたいならupdatedToolOutput、元の結果に注記したいならdecisionかadditionalContextを使います。Claude Codeの外での書き換えまで追うなら、FileChangedを併用します。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。