Practical Recipes
Knowledge
Ready-Made Hook Configurations to Copy
The following recipes can be directly added to your .claude/settings.json. Each solves a concrete problem -- deterministically and reliably.
Auto-Format After Every Change
Problem: Claude sometimes formats code inconsistently -- especially with large changes or when the context is full.
Solution: PostToolUse Hook on Edit and Write.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx prettier --write $TOOL_INPUT_FILE_PATH && npx eslint --fix $TOOL_INPUT_FILE_PATH"
}
]
}
]
}
}
Result: Every file Claude changes or creates is immediately formatted and linted. No more manual cleanup.
*Performance Tip
Prettier and ESLint on individual files are fast (under 1 second). The overhead is minimal, but the quality gain is enormous.
Tests Before Every Commit
Problem: Claude sometimes commits code that breaks tests -- especially during complex refactorings.
Solution: PreToolUse Hook with if filter on git commit.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"if": "Bash(git commit*)",
"hooks": [
{
"type": "command",
"command": "npm test 2>&1; if [ $? -ne 0 ]; then echo 'Tests failed -- commit blocked.' >&2; exit 2; fi"
}
]
}
]
}
}
Result: No commit goes through if tests fail. Claude gets the feedback and automatically fixes the errors.
!Exit Code 2 Is Crucial
Without exit 2, the commit would go through despite failing tests. Exit code 2 blocks the action and gives Claude the feedback via stderr.
TypeScript Check After File Changes
Problem: Claude sometimes produces type errors that only surface at build time.
Solution: PostToolUse Hook on Write for TypeScript files.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "if echo $TOOL_INPUT_FILE_PATH | grep -q '\\.tsx\\?$'; then npx tsc --noEmit 2>&1 | head -20; fi"
}
]
}
]
}
}
Result: Type errors become visible immediately, not at the next build. Claude sees the errors in context and can fix them directly.
Security Scan After New Dependencies
Problem: New npm packages can introduce known security vulnerabilities.
Solution: PostToolUse Hook on Write that runs an audit when package.json changes.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "if echo $TOOL_INPUT_FILE_PATH | grep -q 'package.json'; then npm audit --audit-level=high 2>&1; if [ $? -ne 0 ]; then echo 'Security vulnerabilities found!' >&2; exit 2; fi; fi"
}
]
}
]
}
}
Result: Security vulnerabilities are caught early -- before the code goes to production.
Restore Context After Compact
Problem: After context compaction, Claude forgets important project details that are in CLAUDE.md but get lost in the compacted context.
Solution: PostCompact Hook that re-injects critical information.
{
"hooks": {
"PostCompact": [
{
"hooks": [
{
"type": "command",
"command": "cat .claude/post-compact-context.md"
}
]
}
]
}
}
Create a file .claude/post-compact-context.md with the most important project info:
## Critical Project Context (after Compact)
- We are working on Feature X on branch feature/xyz
- Architecture: Next.js 15 App Router + Zustand
- DO NOT change: src/lib/auth.ts (being worked on by another team)
- Current task: [updated manually]
*Keep Post-Compact Context Small
The file should be short and precise -- 20-30 lines maximum. Too much context after a compact is counterproductive because the compact was meant to free up space.
Desktop Notification When Claude Is Waiting
Problem: Claude is done or waiting for input, but you're in another window.
Solution: Notification Hook with an OS-specific command.
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code is waiting for you!\" with title \"Claude Code\"'"
}
]
}
]
}
}
iPlatform-Specific
The example uses osascript for macOS. For Linux, use notify-send "Claude Code is waiting for you!", for Windows use powershell -Command "New-BurntToastNotification -Text 'Claude Code is waiting for you!'".
Understand
Combining Hooks
The true power of Hooks shows in combination. A typical project configuration looks like this:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "npx prettier --write $TOOL_INPUT_FILE_PATH" },
{ "type": "command", "command": "npx eslint --fix $TOOL_INPUT_FILE_PATH" }
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"if": "Bash(git commit*)",
"hooks": [
{ "type": "command", "command": "npm test" }
]
},
{
"matcher": "Bash",
"if": "Bash(git push*--force*)",
"hooks": [
{ "type": "command", "command": "echo 'Force-push blocked!' >&2 && exit 2" }
]
}
],
"PostCompact": [
{
"hooks": [
{ "type": "command", "command": "cat .claude/post-compact-context.md" }
]
}
]
}
}
This configuration ensures: Code is always formatted, tests run before every commit, force pushes are impossible, and no context is lost after a compact.
Apply
Start with a single Hook -- for example, the auto-format Hook. Make sure it works, then gradually add more. Every Hook you add is one less guarantee you have to think about.
*Debugging Tip
If a Hook doesn't work as expected, test the command manually in the terminal first. The most common errors: wrong path, missing permissions, or missing tool (e.g., Prettier not installed).
Reflect
Hooks transform "I hope Claude does it right" into "I know it runs correctly." This is a fundamental difference in collaborating with AI agents: You give freedom in the solution but set hard boundaries on quality. This principle -- creative freedom within firm guardrails -- is the key to productive work with Claude Code.