Claude Media
PostToolUse hookでツール実行後の後処理を自動化する

PostToolUse hookでツール実行後の後処理を自動化する

Claude CodeのPostToolUse hookでファイル編集後のフォーマット・記録・チェックを自動化する設定手順とつまずきどころ。

PostToolUse hookとは何か

PostToolUse hookは、Claude Codeがツールを実行して成功したあとに動くフックです。ファイルを書き換えたら自動でフォーマットをかける、コマンドの実行結果をログに残す、書き込み内容を外部ツールでチェックする。こうした後処理を毎回手作業で挟まずに済ませるための仕組みです。

似た名前のPreToolUse hookとは役割が逆です。PreToolUseはツールが動く前に呼ばれ、実行そのものを止められます。PostToolUseはツールが動いたあとに呼ばれるので、ファイルの書き込みやコマンドの実行はすでに完了しています。止めることはできず、できるのは後始末とClaudeへのフィードバックだけです。この非対称性を最初に押さえておくと、設定を書くときの迷いが減ります。

前提: hooksの設定階層

PostToolUse hookは.claude/settings.json(プロジェクト単位)、~/.claude/settings.json(全プロジェクト共通)、プラグインのhooks/hooks.json、スキルやサブエージェントのfrontmatterのいずれかに定義します。設定は3段階の入れ子です。

  1. イベント(PostToolUse)を選ぶ
  2. マッチャーで対象ツールを絞る(BashEdit|Writeなど)
  3. 実行するハンドラー(シェルコマンド・HTTPエンドポイント・MCPツール・プロンプト・エージェントのいずれか)を書く

マッチャーはツール名に対して評価されます。英数字・アンダースコア・ハイフン・スペース・カンマ・|だけで構成された文字列は完全一致(または|区切りの複数一致)として扱われ、それ以外の文字を含むと正規表現として評価されます。Edit|WriteEditWriteの両方に一致し、mcp__memory__.*はmemoryサーバーの全ツールに一致します。マッチャーを省略するか"*"にすると、ツールの種類を問わずすべてのPostToolUse呼び出しで発火します。

PostToolUseが受け取る入力

PostToolUse hookには、他のツールイベントと同じtool_nametool_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
}
JSON

duration_msはツールの実行時間(ミリ秒)で、パーミッションプロンプトやPreToolUse hookの待ち時間は含みません。ファイル系ツールのtool_inputのパスは常に絶対パスで、区切り文字はOSのネイティブな形式(Windowsではバックスラッシュ)になります。

設定手順: 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を使います。

記録 — 変更履歴を外部に残す

ツール名を絞らずにmatcher: "*"で発火させ、git status --porcelainのようなコマンドで変更点を自分で調べてログに追記する方法です。公式ドキュメントも、ツール名を限定しない代わりにハンドラー側で変更内容を検出するこのパターンを推奨しています。git diffは追跡外ファイルを拾いませんが、git status --porcelainなら拾えます。

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出力のhookSpecificOutputで、実行済みのツール呼び出しに対してフィードバックを返せます。

フィールドできること
decision: "block" + reasonできることツール結果の横に理由を添えて表示する(結果そのものは変わらない)
additionalContextできることツール結果の横にコンテキストを差し込む。Claudeは次のモデルリクエストで読む
updatedToolOutputできることClaudeに見せるツール出力をまるごと差し替える
classifierContextできることAuto modeの分類器だけに向けた短いメモ(Claude Code v2.1.236以降)

decision: "block"を返してもツールの実行結果自体は変わりません。標準出力(stdout)は{で始まり}で終わるときだけJSONとして解釈され、それ以外はプレーンテキスト扱いです。終了コード0で成功を返し、JSONで構造化した制御を行うのが基本形です。

終了コード2を返すとPostToolUseではブロックはできず、標準エラー出力(stderr)がClaudeに見える形で表示されます(ツールはすでに実行済みのため)。警告をClaudeに気づかせたいだけなら、終了コード2でstderrに書くほうが手早い場面もあります。

Auto modeの分類器向けに使うclassifierContextは、そのクエリがステージング環境に向けて実行されたことを伝える、といった短い事実のメモに限定します。1回のツール呼び出しにつき2,000文字が上限で、設定ファイル・プラグイン・スキル・サブエージェントのfrontmatterから来たメモは「未検証のアプリケーション提供コンテキスト」として扱われ、ユーザーの同意を装う内容は会話本文と突き合わせて確認されます。

PostToolUseとほかのフックの使い分け

目的使うフック理由
ツール実行を止めたい使うフックPreToolUse理由実行前にしか防げない
成功後に整形・記録・チェックしたい使うフックPostToolUse理由実行後にしかtool_responseを受け取れない
ツールが失敗したときだけ動かしたい使うフックPostToolUseFailure理由PostToolUseは成功時のみ発火する
並列実行したツールの結果をまとめて処理したい使うフックPostToolBatch理由個々のPostToolUseとは別に、バッチ単位で発火する
ファイルへの書き込み者を問わず変更を検知したい使うフックFileChanged理由Bashコマンドや外部プロセスによる書き換えも拾える

Claude CodeはEditやWriteでファイルを書き換えたときはPostToolUseを発火しますが、Bashコマンドや外部プロセスが同じファイルを書き換えたときはPostToolUseではなくFileChangedでないと拾えません。監視漏れを防ぎたいなら、ツール経由の変更用にPostToolUse、それ以外の経路も含めた監視用にFileChangedを併用します。

PostToolUseFailure専用の設定手順や、ツール失敗時だけ動くリカバリの書き方はPostToolUseFailure hookでツール失敗時だけ動くリカバリを書くにまとめています。並列実行したツール結果をまとめて処理したい場合はPostToolBatch hookで並列ツール結果をまとめて処理する、承認ダイアログの手前で判定を挟みたい場合はPermissionRequest hookで承認ダイアログの前に判定を挟むが参考になります。

よくあるつまずき

ツールの実行を止められると誤解する

PostToolUseは実行後に呼ばれるフックです。終了コード2を返してもツール呼び出しはブロックされません。「危険なコマンドを未然に防ぎたい」という要件であれば、PreToolUseのpermissionDecision: "deny"を使う必要があります。PostToolUseでできるのは、実行済みの結果に対する後処理とフィードバックだけです。

matcherを絞りすぎて記録漏れが起きる

Edit|Writeだけにマッチさせると、Bashで直接ファイルを書き換えたケースを取りこぼします。変更履歴を網羅的に記録したいなら、マッチャーを"*"にしてgit status --porcelainのようなコマンドでハンドラー側から変更を検出するか、FileChangedを併用します。

stdoutに余計な文字列が混ざりJSONとして解釈されない

シェルのプロファイルがログイン時に何か出力する環境だと、その文字列がJSON出力の前後に混入し、パースに失敗することがあります。標準出力にはJSONオブジェクトだけを出す設計にし、ログはファイルやstderrに書くようにします。

.cmd.batのシムをexec形式で直接呼んで失敗する

Windowsでnpmやnpxがインストールする.cmd.batのシムは実行ファイルではないため、argsを指定したexec形式では起動できません。nodeコマンドと本体スクリプトのパスを直接指定するか、argsを省いたシェル形式で呼び出します。

MCPツールハンドラーがセッション開始直後だけ動かない

type: "mcp_tool"のハンドラーは、Claude CodeがセッションのMCPサーバーをフックから使える状態にしたあとでないと呼び出せません。SessionStartSetupは接続前に発火するため、そこにmcp_toolハンドラーを置いてもエラーを出さずにスキップされます(デバッグログには記録されます)。PostToolUseはツール呼び出しの後に発火するため、この制約に当たることは通常ありません。

まとめ

PostToolUse hookは、Claude Codeがツールを成功させたあとにだけ動く後処理の入口です。マッチャーでツールを絞り、コマンド・HTTP・MCPツールのいずれかのハンドラーでフォーマット・記録・チェックを自動化できます。ただし実行済みのツール呼び出し自体を止めることはできず、できるのはdecisionadditionalContextupdatedToolOutputを使ったClaudeへのフィードバックと、ログや外部サービスへの記録です。実行前に止めたい処理はPreToolUse、失敗したときだけ動かしたい処理はPostToolUseFailureに分けて設計すると、フックの役割が混ざりません。

この記事を共有:XはてブLinkedIn