Claude Code hooks: auto-format, auto-test, and self-heal on every file save
Claude Code hooks: auto-format, auto-test, and self-heal on every file save Claude Code hooks let you run shell commands automatically at key moments in your session — before Claude reads a file, after it writes one, or when a tool call completes. This is how you build a self-healing loop where Claude formats, tests, and fixes code without you having to ask. What are hooks? Hooks are defined in your .claude/settings.json file. They fire at lifecycle events during Claude's execution. { "hooks" : { "PostToolUse" : [ { "matcher" : "Write|Edit|MultiEdit" , "hooks" : [ { "type" : "command" , "command" : "npm run lint --silent" } ] } ] } } Every time Claude writes or edits a file, this runs npm run lint automatically. Claude sees the output and fixes any lint errors before moving on. The four ho
Claude Code hooks: auto-format, auto-test, and self-heal on every file save
Claude Code hooks let you run shell commands automatically at key moments in your session — before Claude reads a file, after it writes one, or when a tool call completes. This is how you build a self-healing loop where Claude formats, tests, and fixes code without you having to ask.
What are hooks?
Hooks are defined in your .claude/settings.json file. They fire at lifecycle events during Claude's execution.
{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "npm run lint --silent" } ] } ] } }{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "npm run lint --silent" } ] } ] } }Enter fullscreen mode
Exit fullscreen mode
Every time Claude writes or edits a file, this runs npm run lint automatically. Claude sees the output and fixes any lint errors before moving on.
The four hook events
Event When it fires
PreToolUse
Before Claude calls a tool
PostToolUse
After Claude calls a tool
Notification
When Claude sends a notification
Stop
When Claude finishes responding
Hook 1: Auto-format on write
This runs Prettier after every file write:
{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "npx prettier --write $CLAUDE_TOOL_INPUT_FILE_PATH 2>/dev/null || true" } ] } ] } }{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "npx prettier --write $CLAUDE_TOOL_INPUT_FILE_PATH 2>/dev/null || true" } ] } ] } }Enter fullscreen mode
Exit fullscreen mode
$CLAUDE_TOOL_INPUT_FILE_PATH is the path of the file Claude just wrote. Prettier runs on exactly that file, not your whole project.
Hook 2: Auto-test on write
{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "npm test -- --testPathPattern=$(basename $CLAUDE_TOOL_INPUT_FILE_PATH .ts) --passWithNoTests 2>&1 | tail -20" } ] } ] } }{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "npm test -- --testPathPattern=$(basename $CLAUDE_TOOL_INPUT_FILE_PATH .ts) --passWithNoTests 2>&1 | tail -20" } ] } ] } }Enter fullscreen mode
Exit fullscreen mode
This runs tests related to whatever file Claude just edited. If the test fails, Claude sees the failure output and fixes it immediately — no prompting needed.
Hook 3: Type-check after every edit
{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "npx tsc --noEmit 2>&1 | head -30" } ] } ] } }{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "npx tsc --noEmit 2>&1 | head -30" } ] } ] } }Enter fullscreen mode
Exit fullscreen mode
TypeScript errors appear after every file write. Claude catches type regressions the moment they happen, not at the end of a long session.
The full self-healing loop
Combine all three:
{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "npx prettier --write $CLAUDE_TOOL_INPUT_FILE_PATH 2>/dev/null; npx tsc --noEmit 2>&1 | head -20; npm test -- --passWithNoTests 2>&1 | tail -10" } ] } ] } }{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "npx prettier --write $CLAUDE_TOOL_INPUT_FILE_PATH 2>/dev/null; npx tsc --noEmit 2>&1 | head -20; npm test -- --passWithNoTests 2>&1 | tail -10" } ] } ] } }Enter fullscreen mode
Exit fullscreen mode
Now every file write triggers:
-
Prettier formats it
-
TypeScript checks for type errors
-
Tests run and output the last 10 lines
Claude reads all of this output and continues fixing until everything is green.
Hook 4: Block dangerous commands
Use PreToolUse to prevent Claude from running commands you don't want:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "echo $CLAUDE_TOOL_INPUT_COMMAND | grep -qE 'rm -rf|DROP TABLE|truncate' && echo 'BLOCKED: dangerous command' && exit 1 || exit 0" } ] } ] } }{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "echo $CLAUDE_TOOL_INPUT_COMMAND | grep -qE 'rm -rf|DROP TABLE|truncate' && echo 'BLOCKED: dangerous command' && exit 1 || exit 0" } ] } ] } }Enter fullscreen mode
Exit fullscreen mode
If the command matches a dangerous pattern, the hook exits with code 1 and Claude sees BLOCKED: dangerous command. The tool call is cancelled.
Hook 5: Notify when Claude finishes
{ "hooks": { "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": "osascript -e 'display notification \"Claude finished\" with title \"Claude Code\"' 2>/dev/null || notify-send 'Claude Code' 'Claude finished' 2>/dev/null || true" } ] } ] } }{ "hooks": { "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": "osascript -e 'display notification \"Claude finished\" with title \"Claude Code\"' 2>/dev/null || notify-send 'Claude Code' 'Claude finished' 2>/dev/null || true" } ] } ] } }Enter fullscreen mode
Exit fullscreen mode
Mac users get a system notification. Linux users get notify-send. You can step away from your desk and come back when Claude is done.
Where to put hooks
Project-level (applies to this repo only):
.claude/settings.json
Enter fullscreen mode
Exit fullscreen mode
Global (applies to all Claude Code sessions):
~/.claude/settings.json
Enter fullscreen mode
Exit fullscreen mode
Start with project-level. The commands will be different per project — your Python project's linter isn't the same as your Node project's linter.
Practical tip: keep hooks fast
Hooks run synchronously. If your test suite takes 3 minutes, don't run all tests on every write. Run only the test file matching the edited file:
npm test -- --testPathPattern=$(basename $CLAUDE_TOOL_INPUT_FILE_PATH .ts) --passWithNoTests 2>&1 | tail -10
Enter fullscreen mode
Exit fullscreen mode
Or run just the linter (fast) on write, and full tests only at Stop:
{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [{"type": "command", "command": "npx prettier --write $CLAUDE_TOOL_INPUT_FILE_PATH 2>/dev/null"}] } ], "Stop": [ { "matcher": "", "hooks": [{"type": "command", "command": "npm test 2>&1 | tail -20"}] } ] } }{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [{"type": "command", "command": "npx prettier --write $CLAUDE_TOOL_INPUT_FILE_PATH 2>/dev/null"}] } ], "Stop": [ { "matcher": "", "hooks": [{"type": "command", "command": "npm test 2>&1 | tail -20"}] } ] } }Enter fullscreen mode
Exit fullscreen mode
Rate limits break the loop
The self-healing loop depends on Claude staying in the session. If you hit Claude's rate limit mid-session, the loop breaks. You're back to manually running lint and tests.
The fix: set ANTHROPIC_BASE_URL to a proxy that removes rate limits.
export ANTHROPIC_BASE_URL=https://simplylouie.com claudeexport ANTHROPIC_BASE_URL=https://simplylouie.com claudeEnter fullscreen mode
Exit fullscreen mode
SimplyLouie is ✌️$2/month. The hooks loop runs uninterrupted — Claude writes, formats, tests, fixes, and finishes without hitting a wall.
Summary
Hook type Use for
PostToolUse + Write matcher
Auto-format, auto-test, type-check
PreToolUse + Bash matcher
Block dangerous commands
Stop
Notifications, full test suite
Hooks turn Claude Code from an AI you prompt into a system that enforces quality automatically. Set them up once, forget about formatting and type errors forever.
Sign in to highlight and annotate this article

Conversation starters
Daily AI Digest
Get the top 5 AI stories delivered to your inbox every morning.
Knowledge Map
Connected Articles — Knowledge Graph
This article is connected to other articles through shared AI topics and tags.
More in Products

Silverback AI Chatbot Announces Development of AI Assistant Feature to Support Automated Digital Interaction and Workflow Management - Corpus Christi Caller-Times
Silverback AI Chatbot Announces Development of AI Assistant Feature to Support Automated Digital Interaction and Workflow Management Corpus Christi Caller-Times







Discussion
Sign in to join the discussion
No comments yet — be the first to share your thoughts!