What are hooks?
Hooks let you intercept and react to Claude Code lifecycle events: before running a tool, after doing so, when Claude finishes a response, and so on.
Common use cases:
- Automatically format code after each edit.
- Log all Claude operations.
- Block dangerous operations with custom logic.
- Send notifications when Claude finishes a long task.
- Run tests automatically after each change.
Hook types
Configuring hooks
Hooks are configured in .claude/settings.json (project) or ~/.claude/settings.json (global):
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "prettier --write $CLAUDE_FILE_PATH"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification "Claude terminó" with title "Claude Code"'"
}
]
}
]
}
}Environment variables available in hooks
The matcher
The matcher field is a regular expression matched against the tool name. If not specified, the hook applies to all tools.
# Solo edición de archivos TypeScript "matcher": "Edit" # Herramientas de bash y edición "matcher": "Bash|Edit|Write" # Cualquier herramienta de lectura "matcher": "^Read"
Command-type hooks
The most common type. Runs a shell command. The hook can read event information via environment variables or via stdin (JSON):
{
"type": "command",
"command": "bash /ruta/a/mi-hook.sh",
"timeout": 30
}Example: hook that reads event data from stdin
#!/bin/bash # mi-hook.sh — recibe el evento completo como JSON por stdin EVENT=$(cat) TOOL=$(echo $EVENT | jq -r '.tool_name') FILE=$(echo $EVENT | jq -r '.tool_input.path // ""') echo "Herramienta: $TOOL, Archivo: $FILE" >> ~/.claude/hook.log
Blocking operations with PreToolUse
A PreToolUse hook can return a non-zero exit code to block the operation:
#!/bin/bash # Bloquear rm -rf TOOL=$(cat | jq -r '.tool_name') CMD=$(cat | jq -r '.tool_input.command // ""') if [[ "$CMD" == *"rm -rf"* ]]; then echo "BLOQUEADO: rm -rf no permitido" exit 1 # exit 1 = bloquear la operación fi exit 0 # exit 0 = permitir
Configure it like this in settings.json:
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "bash ~/.claude/hooks/bloquear-rm.sh"
}]
}]
}
}Practical examples
Format with Prettier after editing
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "npx prettier --write "$CLAUDE_FILE_PATH" 2>/dev/null || true"
}]
}]
}
}Run tests after changes
{
"hooks": {
"Stop": [{
"hooks": [{
"type": "command",
"command": "npm test --watchAll=false 2>&1 | tail -20"
}]
}]
}
}Log all operations
{
"hooks": {
"PostToolUse": [{
"hooks": [{
"type": "command",
"command": "echo "$(date) - $CLAUDE_TOOL_NAME: $CLAUDE_FILE_PATH" >> ~/.claude/audit.log"
}]
}]
}
}Managing hooks from the CLI
# Ver hooks configurados (dentro de Claude Code) /hooks # O abre settings.json directamente: claude config