Claude Tips mascot
Claude Tips & Tricks
Claude Code advanced

Enforce TDD with Hooks That Block Untested Code

Use Claude Code hooks to automatically reject file writes that don't have corresponding tests, enforcing test-driven development.

If you want Claude to always write tests before implementation, don’t just ask. Enforce it with a hook.

The Hook

Add to .claude/settings.json:

{
  "hooks": {
    "preToolExecution": [
      {
        "matcher": { "tool": "Write", "path": "src/**/*.ts" },
        "command": "python3 .claude/scripts/tdd-guard.py",
        "timeout": 5000
      }
    ]
  }
}

Guard Script

#!/usr/bin/env python3
import sys
import json
import os

input_data = json.loads(sys.stdin.read())
file_path = input_data.get("path", "")

# Only check source files, not test files
if ".test." in file_path or ".spec." in file_path or "/tests/" in file_path:
    sys.exit(0)

# Check if a corresponding test file exists
test_variants = [
    file_path.replace(".ts", ".test.ts"),
    file_path.replace(".ts", ".spec.ts"),
    file_path.replace("/src/", "/tests/"),
]

for test_path in test_variants:
    if os.path.exists(test_path):
        sys.exit(0)

print(json.dumps({
    "blocked": True,
    "reason": "Write the test file first. No test found for: " + file_path
}))
sys.exit(1)

What Happens

When Claude tries to write src/auth/login.ts without src/auth/login.test.ts existing, the hook blocks the write and tells Claude to create the test first. Claude then writes the test, and on the next attempt the source file goes through.

Tip

Combine with the TDD workflow tip. Add “Write tests first, then implement” to your CLAUDE.md, and use this hook as the enforcement backstop.