10 Claude Code Hooks That Automate Your Development Workflow
Claude Code hooks are the secret weapon for developers who want to enforce quality standards automatically. These scripts execute when specific events occur—before or after tool calls, on notifications, or when sessions start. Instead of manually running linters, checking for secrets, or validating builds, hooks do it for you.
How Hooks Work
Hooks fire on three event types:
- PreToolUse: Before Claude executes a tool (edit, bash, write)
- PostToolUse: After a tool completes
- Notification: When Claude sends a notification
Configure them in .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit",
"command": "bash .claude/hooks/lint-check.sh"
}
]
}
}
When hooks run, they receive tool input via the $CLAUDE_TOOL_INPUT environment variable as JSON. Use jq to parse it.
Hook 1: Auto-Lint on Every Edit
This PreToolUse hook runs ESLint automatically before any TypeScript/TSX file edit:
#!/bin/bash
# .claude/hooks/lint-check.sh
FILE=$(echo "$CLAUDE_TOOL_INPUT" | jq -r '.file_path // empty')
if [[ "$FILE" == *.ts || "$FILE" == *.tsx ]]; then
npx eslint "$FILE" --fix 2>/dev/null
fi
Why it works: By fixing linting issues before the edit happens, you ensure Claude's changes follow your code style from the start.
Hook 2: Secret Scanner
Block any edit that would introduce API keys or secrets:
#!/bin/bash
INPUT=$(echo "$CLAUDE_TOOL_INPUT" | jq -r '.new_string // empty')
if echo "$INPUT" | grep -qE '(sk-[a-zA-Z0-9]{20,}|AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36})'; then
echo "BLOCKED: Potential secret detected in edit"
exit 1
fi
Critical: The exit 1 stops the tool execution. This prevents accidental commits of secrets.
Hook 3: Test Runner After Changes
A PostToolUse hook that runs tests automatically after file edits:
#!/bin/bash
FILE=$(echo "$CLAUDE_TOOL_INPUT" | jq -r '.file_path // empty')
if [[ "$FILE" == *.ts ]]; then
TEST_FILE="${FILE%.ts}.test.ts"
if [[ -f "$TEST_FILE" ]]; then
npx jest "$TEST_FILE" --silent 2>&1 | tail -5
fi
fi
Pro tip: Use --silent to show only test results, not Jest's verbose output.
Hook 4: Import Verifier
Check that all imports in edited files actually exist:
#!/bin/bash
FILE=$(echo "$CLAUDE_TOOL_INPUT" | jq -r '.file_path // empty')
if [[ "$FILE" == *.ts || "$FILE" == *.tsx ]]; then
npx tsc --noEmit "$FILE" 2>&1 | grep "Cannot find module" | head -5
fi
This catches broken imports immediately, not hours later when you run the build.
Hook 5: Build Validator
Ensure your build still passes after any change:
#!/bin/bash
if [[ -f "package.json" ]]; then
npm run build 2>&1 | tail -3
if [[ $? -ne 0 ]]; then
echo "WARNING: Build failed after last change"
fi
fi
Optimization: Only show the last 3 lines of build output to avoid flooding your terminal.
Hook 6: Git Diff Size Limiter
Warn when uncommitted changes grow too large:
#!/bin/bash
LINES_CHANGED=$(git diff --stat | tail -1 | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+')
if [[ "$LINES_CHANGED" -gt 500 ]]; then
echo "WARNING: $LINES_CHANGED lines changed without commit. Consider committing."
fi
Set this as a PreToolUse hook on "Bash" to catch it before running any shell commands.
Hook 7: Dependency Checker
After editing package.json, verify no vulnerabilities were introduced:
#!/bin/bash
FILE=$(echo "$CLAUDE_TOOL_INPUT" | jq -r '.file_path // empty')
if [[ "$FILE" == *"package.json" ]]; then
npm audit --production 2>&1 | grep -E "vulnerabilities|found"
fi
Production focus: Use --production to ignore dev dependencies.
Hook 8: Documentation Sync
When a function signature changes, flag documentation for updates:
#!/bin/bash
FILE=$(echo "$CLAUDE_TOOL_INPUT" | jq -r '.file_path // empty')
OLD=$(echo "$CLAUDE_TOOL_INPUT" | jq -r '.old_string // empty')
if echo "$OLD" | grep -q "export function\|export const.*=.*=>"; then
README_REF=$(grep -l "$(basename $FILE)" README.md docs/*.md 2>/dev/null)
if [[ -n "$README_REF" ]]; then
echo "NOTE: Function signature changed. Check docs in: $README_REF"
fi
fi
How To Start Using Hooks Today
- Create
.claude/hooks/directory in your project - Add your hook scripts (make them executable with
chmod +x) - Configure in
.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit",
"command": "bash .claude/hooks/lint-check.sh"
},
{
"matcher": "Edit",
"command": "bash .claude/hooks/secret-scanner.sh"
}
],
"PostToolUse": [
{
"matcher": "Edit",
"command": "bash .claude/hooks/test-runner.sh"
}
]
}
}
Matcher options: "Edit", "Bash", "Write", or "*" for all tools.
Advanced Hook Patterns
- Session start hooks: Run when Claude Code starts—perfect for checking environment
- Notification hooks: Trigger when Claude sends notifications
- Conditional execution: Use
$CLAUDE_TOOL_INPUTto decide when to run - Chaining hooks: Multiple hooks can run for the same event
Hooks transform Claude Code from a reactive assistant to a proactive quality enforcer. Start with the secret scanner and auto-linter—they'll save you from the most common mistakes immediately.




