PostToolUseで整形とテストを自動化
PostToolUseはツールが成功した直後に動き、知らせることはできても取り消せません。編集のたびに整形とテストを回すhookを載せます。
- PostToolUse は、ツールの呼び出しが成功した直後に動く hook です。編集したファイルの整形や、テストの失敗を Claude に知らせるのに向いています。
- PostToolUse は何も取り消せません。動作そのものを止めたいなら PreToolUse を使います。
- Edit|Write に当てた hook は、Bash などが同じファイルを書き換えたときには動きません。その場合は FileChanged のイベントを使います。
PostToolUse は、ツールの呼び出しが成功した直後に動く hook です。Claude が書いたファイルを整形したり、テストを走らせて失敗を Claude に知らせたりするのに向いています。この記事では、ダミーの todo アプリを例に、編集したファイルに Prettier をかける hook と、npm test を走らせて失敗を返す hook の2つを載せます。編集部の検証環境(macOS、Claude Code 2.1.306、jq 1.7.1、Prettier 3.9.10、Node.js の組み込みのテストランナー、2026年10月22日)で、スクリプトにダミーの JSON を流して確かめました。実際のセッションの中で発火するところは見ていません(未実行)。ツールを実行の前に止める hook はPreToolUse hookの例集にあります。
PostToolUse でできること・できないこと
PostToolUse は、ツールが成功したあとに動きます。入力の JSON には、Claude がツールに渡した tool_input と、ツールが返した tool_response が入ります。Edit と Write の tool_input.file_path は、いつも絶対パスです。ツールの実行時間が duration_ms として入ることもあります。matcher はツールの名前で絞り込み、書き方は PreToolUse と同じです。
| hook の出力 | 効果 |
|---|---|
exit 0、何も出さない | 何も起きない |
exit 2 と stderr | Claude が stderr を読む。ツールはもう動いている |
JSON の {"decision": "block", "reason": "..."} | ツールの結果の横に reason が足される。元の出力も Claude に見える |
JSON の hookSpecificOutput.additionalContext | Claude の文脈に文が足される |
JSON の hookSpecificOutput.updatedToolOutput | Claude に見えるツールの出力を差し替える |
PostToolUse は何も取り消せません。 updatedToolOutput が変えるのは Claude に見える出力だけです。書いたファイルや実行したコマンドは、もう効いています。動作そのものを止めたいなら PreToolUse を使います。
Edit|Write に当てた PostToolUse は、Bash のコマンドや Claude Code の外のプロセスが同じファイルを書き換えたときには動きません。どこから書き換えられても動かしたいときは、FileChanged のイベントを使うよう公式に書かれています。どのツールのあとでも動かしたいなら matcher を省くか "*" にして、hook の中で git status --porcelain を見る方法も載っています。
出典:https://code.claude.com/docs/en/hooks 、2026-10-22確認
設定の形
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format-on-edit.sh" },
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/test-on-edit.sh", "timeout": 120 }
]
}
]
}
}
この .claude/settings.json は jq を通り、claude doctor でも不正な設定は出ませんでした。当たった hook は並列に動くので、テストの hook が整形の終わる前に動くこともあります。command hook のタイムアウトの既定値は600秒で、テストのほうは120秒にしています。
出典:https://code.claude.com/docs/en/hooks 、2026-10-22確認
編集のたびにファイルを整形する
公式のガイドには、file_path を npx prettier --write に渡す1行の版が載っています(出典:https://code.claude.com/docs/en/hooks-guide 、2026-10-22確認)。ここではスクリプトにして、Prettier が扱わないファイルを飛ばし、失敗を Claude に知らせるようにしました。Prettier は開発用の依存として入れておき、--no-install で、編集のたびに何かがダウンロードされないようにします。
#!/usr/bin/env bash
# .claude/hooks/format-on-edit.sh (PostToolUse, matcher: Edit|Write)
path=$(jq -r '.tool_input.file_path // ""')
case "$path" in
*.js|*.ts|*.json|*.md) ;;
*) exit 0 ;;
esac
cd "${CLAUDE_PROJECT_DIR:?}" || exit 1
if ! out=$(npx --no-install prettier --write "$path" 2>&1); then
echo "prettier failed on $(basename "$path"): $out" >&2
exit 2
fi
exit 0
src/todos.js の Edit を表すダミーの JSON を流す前と後です。
$ cat src/todos.js
export function addTodo(list,title){
return [...list,{title,done:false}]}
$ ... | .claude/hooks/format-on-edit.sh; echo "exit=$?"
exit=0
$ cat src/todos.js
export function addTodo(list, title) {
return [...list, { title, done: false }];
}
Prettier が読めない JSON のファイルを渡すと、exit 2 で終わり、パースのエラーが stderr に出ました(パスの前半は伏せています)。
prettier failed on broken.json: [error] <project>/src/broken.json: SyntaxError: Unexpected keyword 'this'. (1:3)
[error] > 1 | { this is not json
[error] | ^
exit=2
.txt のファイルは何もせずに exit 0 でした。
テストを走らせて失敗を Claude に返す
src/ か test/ の下のファイルが変わったら npm test を走らせる hook です。失敗したら、落ちた行を reason にして decision: "block" を返します。JSON を読ませるために、終了コードは 0 にします。
#!/usr/bin/env bash
# .claude/hooks/test-on-edit.sh (PostToolUse, matcher: Edit|Write)
path=$(jq -r '.tool_input.file_path // ""')
root="${CLAUDE_PROJECT_DIR:?}"
case "$path" in
"$root"/src/*|"$root"/test/*) ;;
*) exit 0 ;;
esac
cd "$root" || exit 1
if out=$(npm test --silent 2>&1); then
exit 0
fi
jq -n --arg r "npm test failed after editing $(basename "$path"):
$(printf '%s\n' "$out" | grep -E '^(✖|ℹ (pass|fail))' | head -3)" \
'{decision: "block", reason: $r}'
exit 0
テストが通るときは、何も出さずに exit 0 でした。次に、addTodo が最初から完了済みの todo を作るように壊してから流しました。
{
"decision": "block",
"reason": "npm test failed after editing todos.js:\n✖ new todo is not done (0.607125ms)\nℹ pass 1\nℹ fail 1"
}
exit=0
README.md のように src/ と test/ の外のファイルでは、テストを走らせずに exit 0 でした。grep のパターンは node --test の出力の形に合わせたものなので、テストランナーに合わせて変えます。reason は失敗するたびに Claude の文脈に入るので、短くまとめます。
実際のセッションで、Claude がこの reason を読んでどう直すかは試していません(未実行)。
テストを毎回走らせないほうがいいとき
編集のたびに全部のテストを走らせると遅くなります。3つのファイルにまたがる変更では、3つ目を直すまでテストが落ち続けます。そういうときは、PostToolUse には速い確認だけを残し、全部のテストは Claude が終わろうとしたときに1回だけ動く Stop hook に回します(書き方はStop hookで完了を確かめる)。hook を使う考え方はClaude Code hooks実践にまとめています。
うまく動かないとき
hook が発火しない、効かないときは、実行権限、パス、matcher の大文字と小文字、exit 1 と exit 2 の違いを確かめます。確かめる順番はhooksが動かないときの確認にまとめています。
まとめ
- PostToolUse はツールが成功した直後に動く。知らせることはできるが、取り消すことはできない
- matcher は
Edit|Write。Bash で同じファイルを書き換えても発火しない - テストの失敗は、短い
reasonを付けたdecision: "block"で、ツールの結果の横に出す - 整形のエラーは
exit 2で stderr を Claude に渡す - 整形のツールは手元に入れ、
npx --no-installで呼ぶ - 全部のテストのような重い確認は Stop hook に回す