5 Production Patterns That Actually Matter
The most impactful hook you can set up. Zero effort, massive consistency gains.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$TOOL_INPUT_FILE_PATH\" 2>/dev/null; exit 0",
"timeout": 10
}
]
}
]
}
}
The exit 0 at the end is critical — PostToolUse hooks with non-zero exits don't block anything but they do add noise to Claude's context.
Pattern 2: Security Gate for Dangerous Commands
This is the one that bit me. Here's the correct version:
#!/bin/bash
# .claude/hooks/block-dangerous.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# Block destructive patterns
if echo "$COMMAND" | grep -iE '\b(rm\s+-rf|DROP\s+TABLE|DELETE\s+FROM|TRUNCATE|FORMAT)\b' > /dev/null; then
echo "BLOCKED: Destructive command detected: $COMMAND" >&2
exit 2 # EXIT 2 = BLOCK. Not exit 1!
fi
exit 0
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/block-dangerous.sh",
"timeout": 5
}
]
}
]
}
}
Pattern 3: Auto-Test After Code Changes
Run tests automatically whenever Claude modifies a source file:
#!/bin/bash
# .claude/hooks/auto-test.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Only run tests for source files
if [[ "$FILE_PATH" == *.ts ]] || [[ "$FILE_PATH" == *.tsx ]]; then
# Find related test file
TEST_FILE="${FILE_PATH%.ts}.test.ts"
TEST_FILE="${TEST_FILE%.tsx}.test.tsx"
if [ -f "$TEST_FILE" ]; then
RESULT=$(npx vitest run "$TEST_FILE" --reporter=json 2>&1)
if [ $? -ne 0 ]; then
echo "Tests failed for $TEST_FILE" >&2
echo "$RESULT" | tail -20 >&2
# Don't exit 2 — we want Claude to see the failure and fix it
exit 0
fi
fi
fi
exit 0
Note: this uses PostToolUse, not PreToolUse, because we want tests to run after the edit completes.
Pattern 4: Cost Tracker
Track token usage across sessions with an HTTP hook:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "http",
"url": "http://localhost:3001/api/claude-usage",
"timeout": 5
}
]
}
]
}
}
The Stop event includes last_assistant_message — parse it to estimate tokens and costs. I run a simple Express server that logs to SQLite. After a month you'll have real data on where your budget goes.
Pattern 5: Slack Notification on Completion
Alert your team when Claude finishes a long task:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "curl -s -X POST -H 'Content-Type: application/json' -d '{\"text\":\"Claude finished a task in '\"$PWD\"'\"}' $SLACK_WEBHOOK_URL",
"async": true,
"timeout": 10
}
]
}
]
}
}
The async: true flag is key — it fires the notification without blocking Claude's response.