Skip to content

How Do I Set Up Hooks in Claude Code to Run Tests Automatically After Edits?

I spent 20 minutes debugging a pile of broken code. Again. Claude Code had made 15 edits across multiple files, and by the time I ran my tests, I had no idea which change caused the failures.

There had to be a better way. Turns out, there is: PostToolUse hooks that run tests automatically after every edit.

The Problem: Accumulating Broken Code

When working with Claude Code, it’s easy to let broken code pile up:

  • Claude makes 10-15 edits across several files
  • You run tests at the end of the session
  • Tests fail, but you’ve lost the context of what went wrong
  • You spend ages figuring out which edit broke what

I’d seen this pattern too many times. Each debugging session felt like archaeology - digging through layers of changes, trying to reconstruct what happened.

The Solution: PostToolUse Hooks

Claude Code hooks let you run commands automatically in response to events. A PostToolUse hook triggers after file modifications (Write or Edit tools), giving you immediate test feedback.

Here’s my first attempt at setting this up.

I added this to my .claude/settings.json:

settings.json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npm test",
"timeout": 30000
}
]
}
]
}
}

This worked, but it ran my entire test suite after every single edit. That was slow and noisy. Most of the time, I only cared about tests related to the file I just edited.

Jest has a --findRelatedTests flag that finds and runs only the tests for a specific file. I needed to pass the edited file path to this command.

Claude Code provides environment variables for hook commands. The TOOL_INPUT_FILE_PATH variable contains the path of the file that was just edited.

settings.json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash -c 'file=\"$TOOL_INPUT_FILE_PATH\"; if [[ \"$file\" == *.ts || \"$file\" == *.tsx ]]; then npx jest --findRelatedTests \"$file\" --passWithNoTests --silent 2>&1 || true; fi'",
"timeout": 45000
}
]
}
]
}
}

A few important details here:

  1. The || true at the end - Without this, a failing test would block Claude’s workflow entirely. We want to see the failure but let Claude continue.

  2. The file extension check - I only run tests for TypeScript files. No need to run tests when editing a README.

  3. The --passWithNoTests flag - Some files don’t have related tests yet. This prevents Jest from failing when that happens.

Why This Changes Everything

After using this setup for a week, the difference is stark.

Before: I’d make changes, get distracted, come back hours later, run tests, and spend 30 minutes debugging.

After: Tests run automatically. I see failures immediately. Claude still has the context of what it just did. It can fix its own mistakes right away.

The Reddit thread that pointed me in this direction said it well: “I kick off a task and come back an hour later to something that actually works.”

Common Mistakes I Made

Mistake 1: Running all tests every time

My first hook ran npm test on every edit. This was slow and produced too much noise. Use --findRelatedTests (Jest) or run tests for specific modules (pytest).

Mistake 2: Forgetting || true

When a test fails, the command exits with a non-zero status. Without || true, this can block Claude’s workflow. We want feedback, not roadblocks.

Mistake 3: No timeout

Long-running tests can block your session. Set an appropriate timeout. I use 45 seconds for Jest and 60 seconds for pytest.

Python Setup (If You’re Not Using JavaScript)

For Python projects with pytest, here’s the equivalent setup:

settings.json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash -c 'file=\"$TOOL_INPUT_FILE_PATH\"; if [[ \"$file\" == *.py ]]; then pytest -x --tb=short -q 2>&1 || true; fi'",
"timeout": 60000
}
]
}
]
}
}

The -x flag stops after the first failure, which is usually what you want when debugging.

Adding Type Checking

I also added a type-check hook for TypeScript projects. Now I catch type errors immediately:

settings.json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx tsc --noEmit 2>&1 || true",
"timeout": 30000
},
{
"type": "command",
"command": "npx jest --findRelatedTests \"$TOOL_INPUT_FILE_PATH\" --passWithNoTests 2>&1 || true",
"timeout": 45000
}
]
}
]
}
}

This runs type checking first, then tests. Both give feedback without blocking the workflow.

Key Takeaways

PostToolUse hooks for automatic testing have become essential to my workflow:

  • Immediate feedback - Know within seconds if an edit broke something
  • Smaller debug scope - When tests fail, you know exactly which edit caused it
  • Self-correction - Claude can fix its own mistakes while context is fresh
  • Async-friendly - Start a task, walk away, return to working code

Add this configuration to your .claude/settings.json and stop debugging 20-minute piles of changes.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments