Issue 00 · Autumn 2026日本語 · RSS
AgentKoji

Claude Code PostToolUse: Format and Test on Edit

A PostToolUse hook runs after the tool has already acted, so it can report but not undo. Two hooks we tested: run Prettier and run the tests after every edit.

TL;DR

  • A PostToolUse hook runs right after a tool call succeeds, making it the place to format edited files or run tests and report failures to Claude.
  • PostToolUse can't undo anything: exit 2 or a block decision only shows Claude a reason, because the tool already ran. Use PreToolUse to prevent actions.
  • An Edit|Write hook doesn't fire when Bash rewrites the same file; the docs point to the FileChanged event for that. The default command-hook timeout is 600 seconds.

A PostToolUse hook runs right after a tool call succeeds. That makes it the place to format a file Claude just wrote, or to run the tests and tell Claude when they fail. Below are two hooks for a dummy todo app: one runs Prettier on every edited file, the other runs npm test and feeds failures back to Claude. We tested on macOS with Claude Code 2.1.304, jq 1.7.1, Prettier 3.9.10 and Node’s built-in test runner, on 2026-10-20. We piped dummy JSON into the scripts; we did not watch them fire in a live session. For hooks that stop a tool call before it runs, see Claude Code PreToolUse Hook Examples.

What PostToolUse can and can’t do

PostToolUse fires after a tool has finished successfully. Its input includes tool_input (what Claude sent) and tool_response (what the tool returned). For Edit and Write, tool_input.file_path is always an absolute path. The matcher filters on the tool name, the same as PreToolUse (source: https://code.claude.com/docs/en/hooks, checked 2026-10-20).

OutputEffect
exit 0, nothing printedNothing happens
exit 2 with stderrClaude sees the stderr; the tool already ran
JSON {"decision": "block", "reason": "..."}The reason is added next to the tool result
JSON hookSpecificOutput.additionalContextText added to Claude’s context
JSON hookSpecificOutput.updatedToolOutputReplaces what Claude sees as the tool’s output

PostToolUse can’t undo anything. updatedToolOutput changes only what Claude sees; files written and commands run have already taken effect. To prevent an action, use PreToolUse (same source).

A PostToolUse hook on Edit|Write doesn’t fire when a Bash command rewrites the same file. For changes from any source, the docs point to the FileChanged event (same source).

The settings.json

{
  "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 doctor reported no invalid settings for this file. Matching hooks run in parallel, so the test hook may run before or after the formatter finishes. The default timeout for a command hook is 600 seconds; we set 120 for the test run (source: https://code.claude.com/docs/en/hooks, checked 2026-10-20).

Format the file after every edit

The hooks guide has a one-line version that pipes file_path into npx prettier --write (source: https://code.claude.com/docs/en/hooks-guide, checked 2026-10-20). We use a script so we can skip file types Prettier doesn’t handle and report failures. Prettier is installed as a dev dependency, so --no-install never downloads anything at edit time.

#!/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

Before and after, with {"tool_name":"Edit","tool_input":{"file_path":"/home/dev/my-app/src/todos.js"}} piped in:

$ 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 }];
}

When Claude writes a JSON file Prettier can’t parse, the hook exits 2 and Claude gets the parser error:

prettier failed on broken.json: [error] src/broken.json: SyntaxError: Unexpected keyword 'this'. (1:2)
exit=2

Run the tests and tell Claude what failed

This hook runs npm test when a file under src/ or test/ changes. On failure, it returns decision: "block" with the failing lines as the reason, and exits 0 so the JSON is read.

#!/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

With passing tests it printed nothing and returned exit 0. We then broke addTodo so new todos start as done, and added a test for it:

{
  "decision": "block",
  "reason": "npm test failed after editing todos.js:\n✖ new todo is not done (0.294125ms)\nℹ pass 1\nℹ fail 1"
}
exit=0

The grep pattern matches the output format of node --test. Adjust it for your test runner. Keep the reason short; it goes into Claude’s context on every failing edit.

How Claude reacts to the block reason in a live session: (not tested in a live session).

When to put tests in Stop instead

Running the full suite after every edit gets slow, and a change spread across three files fails until the third edit lands. If that happens, run only fast checks in PostToolUse and move the full suite to a Stop hook that runs once, when Claude tries to finish. See Claude Code Stop Hook: Verify Before It Stops. For the reasoning behind using hooks instead of prose rules, see Claude Code Hooks in Practice.

Summary