Análisis completo del mecanismo de Claude Code Hooks: cinco tipos de eventos, control de códigos de salida y cuatro configuraciones reales para auditar, interceptar y automatizar acciones de IA.
Cada vez que Claude Code lee un archivo, escribe código o ejecuta un comando, hay un sistema de eventos funcionando en segundo plano. Los Hooks son la interfaz para conectarse a ese sistema — puedes inyectar tu propia lógica en cualquier momento: ejecutar linters automáticamente, registrar operaciones, interceptar acciones peligrosas o disparar cualquier comando de shell.
Este artículo cubre en su totalidad el mecanismo de Hooks, su configuración y uso práctico.
Los Hooks son comandos de shell configurados en settings.json que Claude Code ejecuta automáticamente cuando ocurren eventos específicos.
La analogía más directa: los git hooks. Git puede disparar scripts antes y después de operaciones como commit o push. Los Claude Code Hooks funcionan exactamente igual — la diferencia es que los puntos de disparo son las llamadas a herramientas de la IA.
¿Por qué importa esto?
Cuanto más capaz se vuelve Claude Code, más necesitas una capa de control determinista. Los Hooks proporcionan:
- Garantías de ejecución que no dependen del prompt (Claude puede ignorar instrucciones, pero los hooks siempre se ejecutan)
- Registros de operaciones auditables
- Comprobaciones de calidad automatizadas
| Tipo | Disparador | Uso típico |
|---|---|---|
PreToolUse |
Antes de una llamada a herramienta | Bloquear operaciones peligrosas, registrar intención |
PostToolUse |
Después de una llamada a herramienta | Auto-lint, ejecutar tests |
PreCompact |
Antes de la compactación del contexto | Guardar snapshot del estado actual |
Notification |
Cuando Claude envía una notificación | Alertas de escritorio, mensajes de Slack |
Stop |
Cuando Claude termina de responder | Resumir logs, disparar flujos posteriores |
Los más usados son PreToolUse y PostToolUse, para interceptar y post-procesar llamadas a herramientas.
Los Hooks van en ~/.claude/settings.json (global) o en .claude/settings.json en la raíz del proyecto (nivel proyecto):
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npm run lint --silent"
}
]
}
]
}
}
Tres campos clave:
matcher: Regex que coincide con nombres de herramientas, determina qué llamadas disparan este hook. "Write|Edit" se activa al escribir o editar archivos. Déjalo vacío o usa ".*" para coincidir con todas las herramientas.type: Por ahora solo "command".command: Cualquier comando de shell.Nombres de herramientas que necesitas para escribir matchers:
| Nombre | Acción |
|---|---|
Write |
Escribir un nuevo archivo |
Edit |
Editar un archivo |
Bash |
Ejecutar un comando de shell |
Read |
Leer un archivo |
Glob |
Búsqueda de archivos |
Grep |
Búsqueda de contenido |
TodoWrite |
Actualizar lista de tareas |
Durante la ejecución, los datos se pasan como JSON por stdin:
{
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.rb",
"content": "..."
},
"tool_response": "..."
}
PreToolUse recibe tool_input (los argumentos antes de la llamada). PostToolUse también recibe tool_response (el valor de retorno de la herramienta).
Ejemplo de hook con lógica condicional:
#!/bin/bash
input=$(cat)
file=$(echo "$input" | jq -r '.tool_input.file_path')
# Solo ejecutar rubocop en archivos .rb
if [[ "$file" == *.rb ]]; then
rubocop "$file" --autocorrect-all --no-color
fi
El código de salida del hook controla el comportamiento posterior de Claude Code:
| Código de salida | Significado |
|---|---|
0 |
Éxito, continuar ejecución |
2 |
Bloquear: cancelar la llamada a herramienta actual, enviar stderr a Claude |
| Otros no-cero | Registrar el error, pero continuar |
El código de salida 2 es el más poderoso — te permite escribir lógica de intercepción en PreToolUse para detener a Claude de realizar una operación y explicarle por qué.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash -c 'file=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path 2>/dev/null); [[ \"$file\" == *.rb ]] && rubocop -A \"$file\" --no-color -q || true'"
}
]
}
]
}
}
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c 'cmd=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .command); if echo \"$cmd\" | grep -qE \"rm.*/(migrations|seeds)\"; then echo \"No está permitido eliminar los directorios migrations o seeds\" >&2; exit 2; fi'"
}
]
}
]
}
}
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|Bash",
"hooks": [
{
"type": "command",
"command": "echo \"$(date '+%Y-%m-%d %H:%M:%S') $CLAUDE_TOOL_NAME: $(echo $CLAUDE_TOOL_INPUT | jq -c .)\" >> ~/.claude/audit.log"
}
]
}
]
}
}
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude ha terminado\" with title \"Claude Code\"'"
}
]
}
]
}
}
Los Hooks pueden ir en dos lugares:
Global (~/.claude/settings.json): Reglas comunes a todos los proyectos — logs, notificaciones.
Nivel proyecto (.claude/settings.json): Comprobaciones específicas del proyecto. Los hooks de proyecto y los globales se combinan y ambos se ejecutan.
En proyectos de equipo, haz commit del settings.json del proyecto en git para que todos ejecuten los mismos hooks.
Pasos para resolver problemas cuando un hook no se activa:
write no es Writeecho "hook triggered" >> /tmp/hook.log para confirmar el disparoEl valor central de los Hooks es conectar el comportamiento impredecible de la IA con los estándares de ingeniería deterministas. Claude puede olvidar ejecutar los tests, pero un PostToolUse hook no. Claude puede eliminar archivos por error, pero un interceptor PreToolUse no lo permitirá.
Empieza con un hook: auto-lint al escribir archivos. Una vez que funcione, agrega más.