Run the nearest test after an edit
Finds the test file that belongs to the file Claude just edited and runs it, in the background.
PostToolUseEdit|Writereview riskneeds manual checkWhat it does
Maps the edited file to its test twin using common conventions (foo.ts -> foo.test.ts, foo.py -> test_foo.py), runs that test with the right runner (vitest/jest via npx, or pytest), and reports failures back to Claude. Runs async so editing never waits on a test suite. Path mapping is heuristic - extend the case list for your layout.
When it runs
| Event | PostToolUse |
| Matcher | Edit|Write |
| Scope | project |
| Platforms | macos, linux |
| Async | runs in the background, reports on exit |
| Timeout | 120s |
| Requires | jq |
| Failure mode | warn |
| Verified against | Claude Code hooks reference, event/matcher schema checked 2026-09-10 |
| Test status | needs manual check - run the test steps below before relying on it |
Config
Merge this into .claude/settings.json - or use the generator to combine hooks.
{
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/run-nearest-test.sh",
"timeout": 120,
"async": true
}
],
"matcher": "Edit|Write"
}
]
}
}Script
Save as .claude/hooks/run-nearest-test.sh and chmod +x it.
#!/bin/bash
# run-nearest-test: run the test file paired with the edited file (async).
set -u
file=$(jq -r '.tool_input.file_path // empty')
[ -z "$file" ] && exit 0
dir=$(dirname "$file"); base=$(basename "$file"); stem="${base%.*}"
candidates=""
case "$base" in
*.test.*|*.spec.*|test_*.py) exit 0 ;; # edited file IS a test; skip
*.ts|*.tsx|*.js|*.jsx) candidates="$dir/$stem.test.ts $dir/$stem.test.tsx $dir/$stem.test.js $dir/$stem.spec.ts" ;;
*.py) candidates="$dir/test_$stem.py $dir/${stem}_test.py" ;;
*) exit 0 ;;
esac
test_file=""
for c in $candidates; do [ -f "$c" ] && test_file="$c" && break; done
[ -z "$test_file" ] && exit 0
cd "${CLAUDE_PROJECT_DIR:-$PWD}" || exit 0
case "$test_file" in
*.py)
command -v pytest >/dev/null 2>&1 || exit 0
if ! pytest -q "$test_file" >/dev/null 2>&1; then
echo "Tests failed in $test_file after editing $file" >&2
pytest -q "$test_file" 2>&1 | tail -20 >&2
exit 2
fi
;;
*)
if ! npx vitest run "$test_file" >/dev/null 2>&1 && ! npx jest "$test_file" >/dev/null 2>&1; then
echo "Tests failed in $test_file after editing $file" >&2
exit 2
fi
;;
esac
exit 0
Install
- Save the script to .claude/hooks/run-nearest-test.sh and chmod +x it.
- Add the config fragment to .claude/settings.json. async: true is intentional - the hook runs in the background and reports on exit.
Test it
- Positive: break an assertion in a paired test, have Claude edit the source file - Claude should get the failure report.
- Negative: edit a file with no test twin - nothing runs.
- Negative: edit the test file itself - the hook skips it to avoid loops.
Sources
- Claude Code hooks reference (checked 2026-09-10)
Related hooks
Typecheck after TypeScript changes
Runs tsc --noEmit in the background after Claude edits TypeScript, so type errors surface even when tests don't cover the file.
PostToolUseEdit|Writereview riskneeds manual checkFull check before task completion
Blocks Claude from marking a task completed while your project check fails.
TaskCompletedblocking riskneeds manual checkPro Pack (later)
Framework bundles, cross-platform scripts, tested blocking policies, and a test harness. Free recipes and the generator stay free.
Waitlist opens soon.