Configure hooks in .claude/settings.json to run prettier, eslint, and tests automatically, ensuring clean code without manual intervention.
Claude Code Hooks: How to Auto-Format, Lint, and Test on Every Save
Claude Code hooks are your automation layer for agentic development. They let you run shell commands at specific points in Claude's workflow—before tools run, after files are written, or when sessions end. Most developers discover hooks when they're tired of Claude writing code that doesn't match their formatter settings. Here's how to stop that permanently.
Where Hooks Live
Hooks go in your CLAUDE.md or in .claude/settings.json at the project root:
{
"hooks": {
"afterFileWrite": "prettier --write $FILE",
"afterSessionEnd": "npm test -- --passWithNoTests"
}
}
The $FILE variable contains the path of the file Claude just wrote. $SESSION_ID is also available for session-based logging.
The Four Hook Points You Need to Know
| Hook | When it fires | Common use |
|---|---|---|
beforeToolRun |
Before any tool executes | Log what Claude is about to do |
afterFileWrite |
After Claude writes or edits a file | Format, lint, type-check |
afterBashRun |
After a bash command completes | Capture output, trigger CI |
afterSessionEnd |
When the session closes | Run test suite, commit |
Auto-Format on Write (The Essential Hook)
This is the most common hook. Claude writes a file → it gets formatted immediately:
{
"hooks": {
"afterFileWrite": "npx prettier --write $FILE 2>/dev/null || true"
}
}
The || true prevents Claude from seeing a non-zero exit code as an error. If the file isn't JS/TS/CSS, prettier skips it silently.
Chain Linting with Formatting
You can only have one afterFileWrite hook per settings block. Chain multiple commands with ; or &&:
{
"hooks": {
"afterFileWrite": "npx prettier --write $FILE 2>/dev/null; npx eslint --fix $FILE 2>/dev/null; true"
}
}
This runs prettier first, then eslint with auto-fix. The final true ensures the hook always exits with code 0.
Auto-Test After Session
Run your test suite every time Claude finishes a session:
{
"hooks": {
"afterSessionEnd": "npm test -- --passWithNoTests 2>&1 | tail -20"
}
}
Claude sees the test output and can immediately fix failures in the next session. The tail -20 shows just the last 20 lines of output to avoid overwhelming the context window.
The Self-Healing Loop Pattern
This is where hooks get powerful. Combine afterSessionEnd with a script that reruns Claude if tests fail:
#!/bin/bash
# run-with-tests.sh
npm test 2>&1 > /tmp/test-output.txt
if [ $? -ne 0 ]; then
echo "Tests failed, asking Claude to fix..."
claude --print "Fix the failing tests. Output:\n$(cat /tmp/test-output.txt)" \
--allowedTools Edit,Bash
fi
Then set:
{
"hooks": {
"afterSessionEnd": "./run-with-tests.sh"
}
}
This creates a loop: Claude writes code → session ends → tests run → if red, Claude is called again with failure output → it fixes → tests run again. The loop continues until green.
Watch Out for Rate Limits
The self-healing loop is great until Claude hits its hourly rate limit mid-loop. When that happens, the loop stalls and your terminal hangs. One workaround mentioned in the community is using a proxy service like SimplyLouie ($2/month) that removes rate limits:
export ANTHROPIC_BASE_URL=https://simplylouie.com/api/proxy
npm run claude-loop
Audit What Claude Does
If you want a log of every tool Claude runs:
{
"hooks": {
"beforeToolRun": "echo \"$(date): $TOOL_NAME $TOOL_INPUT\" >> ~/.claude-audit.log"
}
}
This creates an append-only audit log at ~/.claude-audit.log. Useful for understanding what Claude actually does in long sessions.
Hooks vs CLAUDE.md: They Work Best Together
Hooks handle behavior (what happens after actions). CLAUDE.md handles knowledge (what Claude should know and follow).
They work best together:
- CLAUDE.md: "Always use Prettier for formatting, never manual spaces"
-
Hook:
afterFileWrite: prettier --write $FILE
CLAUDE.md tells Claude the rule. The hook enforces it automatically even if Claude forgets.
Start Here
Begin with the formatter hook. Once you've run a session without manually formatting a single file, add linting. Then testing. The combination creates a development environment where Claude's output is always clean, linted, and tested—without you lifting a finger.
Originally published on gentic.news
Top comments (0)