Los hooks de Claude Code son comandos que Claude Code ejecuta por sí mismo en puntos fijos de una sesión: antes de llamar a una herramienta, después de editar un archivo, al enviar un prompt, cuando Claude termina o necesita tu atención. Los defines bajo la clave hooks de settings.json. Un hook puede añadir contexto, formatear un archivo o bloquear una acción saliendo con código 2 o devolviendo una decisión en JSON. A diferencia de las instrucciones de CLAUDE.md, un hook siempre se ejecuta, porque lo ejecuta el harness, no el modelo.
Esta guía cubre los eventos que importan, el formato de configuración, los matchers, los códigos de salida y el JSON, tres hooks que puedes copiar hoy y las notas de seguridad que conviene leer antes de añadir ninguno. Los datos salen de la referencia de hooks y de la guía de hooks oficiales. Si solo quieres la definición corta, consulta la entrada del glosario sobre hooks de Claude Code.
Por qué usar hooks en lugar de instrucciones
Puedes escribir «ejecuta siempre Prettier después de editar» en CLAUDE.md, y Claude lo hará casi siempre. «Casi» es el problema. Las instrucciones son consejos que el modelo sopesa frente a todo lo demás en su contexto. Los hooks son código que se ejecuta cada vez que salta el evento.
Eso hace de los hooks la herramienta adecuada para tres tipos de reglas:
- Cosas que deben ocurrir siempre, como formatear, pasar el linter o registrar logs.
- Cosas que nunca deben ocurrir, como editar
.envo ejecutarrm -rfen la carpeta equivocada. - Cosas de las que quieres enterarte, como que Claude esté esperando una respuesta de permisos mientras tú estás en otra ventana.
Deja los consejos de estilo y el conocimiento del proyecto en CLAUDE.md y pon las reglas estrictas en hooks.
Eventos de hooks que de verdad vas a usar
La referencia enumera más de treinta eventos, y la lista crece con cada versión. La mayoría de las configuraciones reales usan un puñado:
| Evento | Cuándo se dispara | ¿Puede bloquear? |
|---|---|---|
PreToolUse | Antes de ejecutar una llamada a una herramienta | Sí, bloquea la llamada |
PostToolUse | Después de que una llamada a una herramienta termina bien | No, la herramienta ya se ejecutó; stderr llega a Claude |
UserPromptSubmit | Cuando envías un prompt | Sí, el prompt nunca llega a Claude |
Notification | Cuando Claude Code envía una notificación, como una petición de permiso o una espera por inactividad | No |
Stop | Cuando Claude termina su respuesta | Sí, Claude sigue trabajando |
SubagentStart / SubagentStop | Cuando un subagente empieza o termina | Solo SubagentStop |
SessionStart | Al arrancar, reanudar, con /clear o al compactar | No, pero stdout pasa a ser contexto |
PreCompact | Antes de compactar el contexto | Sí, bloquea la compactación |
PermissionRequest | Cuando está a punto de mostrarse un diálogo de permisos | Solo mediante JSON, no con exit 2 |
SessionEnd | Cuando termina la sesión | No |
PreToolUse y PostToolUse cubren la mayoría de las necesidades. Notification y Stop son los de «avísame cuando termine o se atasque».
Cómo se configuran los hooks en settings.json
Los hooks viven en los mismos archivos de ajustes que los permisos:
| Archivo | Alcance | ¿Se comparte con git? |
|---|---|---|
~/.claude/settings.json | Todos tus proyectos | No |
.claude/settings.json | Un proyecto | Sí |
.claude/settings.local.json | Un proyecto, solo tú | No (en gitignore) |
| Ajustes de política gestionada | Toda la organización | Los fijan los administradores |
Los plugins pueden incluir hooks en hooks/hooks.json, y las skills y los subagentes pueden definir hooks en su frontmatter.
La estructura tiene tres niveles: el evento, un grupo de matcher y una lista de handlers.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write",
"timeout": 30
}
]
}
]
}
}
El type de un handler puede ser command (un comando de shell), http (un POST a una URL), mcp_tool (una herramienta de un servidor MCP que hayas conectado), prompt (una comprobación rápida de sí/no hecha por un modelo) o agent (una comprobación experimental con un subagente). El tiempo de espera por defecto es de 600 segundos para los handlers de comando, HTTP y herramienta MCP, de 30 segundos para los hooks de prompt y de 60 para los de agente.
Cada hook recibe JSON por stdin. Los campos comunes son session_id, transcript_path, cwd, permission_mode y hook_event_name. Los eventos de herramienta añaden tool_name, tool_input y tool_use_id. Tu script lee ese JSON, normalmente con jq, y decide qué hacer. La variable de entorno CLAUDE_PROJECT_DIR apunta a la raíz del proyecto, útil para referenciar scripts guardados en el repositorio.
Puedes escribir /hooks dentro de Claude Code para ver qué hay cargado y de qué archivo viene cada hook. El navegador es de solo lectura, así que los hooks se cambian editando el JSON.
Matchers: qué llamadas a herramientas ve un hook
El matcher decide qué llamadas activan un hook. En los eventos de herramienta, coincide con el nombre de la herramienta:
| Matcher | Significado |
|---|---|
"*", "" o sin matcher | Todas las llamadas |
"Bash" | Exactamente la herramienta Bash |
"Edit|Write" | Edit o Write (una lista) |
"mcp__memory__.*" | Cualquier herramienta del servidor MCP memory |
"mcp__.*__write.*" | Cualquier herramienta MCP cuyo nombre empiece por write |
La regla es sencilla. Si el matcher solo tiene letras, dígitos, _, -, espacios, comas o |, es un nombre exacto o una lista de nombres. Cualquier otro carácter lo convierte en una expresión regular de JavaScript. Las herramientas MCP se llaman mcp__<servidor>__<herramienta>.
Algunos eventos ignoran el matcher, entre ellos UserPromptSubmit y Stop, porque no hay nada con lo que comparar. Otros comparan algo distinto del nombre de una herramienta. SessionStart compara cómo empezó la sesión (startup, resume, clear, compact), y Notification compara el tipo de notificación, como permission_prompt o idle_prompt.
Los eventos de herramienta también aceptan un campo if con la sintaxis de las reglas de permisos, de modo que un hook de Bash se ejecute solo para, por ejemplo, "Bash(git *)". La documentación lo describe como un filtro de mejor esfuerzo, así que deja la comprobación real en tu script.
Códigos de salida y JSON
Un hook de tipo comando se comunica con Claude Code de dos maneras: el código de salida y, opcionalmente, JSON por stdout.
| Código de salida | Qué ocurre |
|---|---|
0 | Éxito. stdout se interpreta como JSON si es un único objeto JSON. En UserPromptSubmit y SessionStart, el texto plano de stdout se añade al contexto de Claude. |
2 | Error bloqueante. stderr es el motivo. En PreToolUse se cancela la llamada, en UserPromptSubmit se rechaza el prompt, en Stop Claude sigue. |
Cualquier otro, incluido 1 | Error no bloqueante. Claude Code lo anota y continúa. |
La trampa aquí es el código de salida 1. Muchos scripts fallan con 1 por defecto, y eso no bloquea nada. Si quieres decir «para», sal con 2.
Para un control más fino, sal con 0 e imprime JSON. Un hook PreToolUse puede permitir, denegar o preguntar:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Use rg instead of grep for better performance"
}
}
permissionDecision admite allow, deny, ask o defer. Un hook también puede reescribir la llamada con updatedInput, o pasarle texto extra a Claude con additionalContext. Los campos que funcionan en cualquier evento incluyen continue: false (detiene a Claude por completo, con un stopReason) y systemMessage (un aviso que te muestra a ti).
Cuando coinciden varios hooks, se ejecutan en paralelo y gana la respuesta más restrictiva: deny gana a defer, defer gana a ask, ask gana a allow. Un allow de un hook no anula una regla deny de tus permisos.
Ejemplo 1: formatear archivos después de cada edición
Es el hook con el que empieza la mayoría. Ejecuta Prettier sobre cualquier archivo que Claude escriba o edite:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
Cambia por ruff format, gofmt -w o rustfmt para otros lenguajes. Como el formateador se ejecuta después de la edición, Claude ve el archivo ya formateado la próxima vez que lo lee, y dejas de ver diffs de puro estilo en la revisión.
Ejemplo 2: bloquear comandos peligrosos y archivos protegidos
Un hook PreToolUse es el sitio para los límites estrictos. Este script, adaptado de la guía oficial, bloquea las ediciones en archivos sensibles. Guárdalo como .claude/hooks/protect-files.sh:
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
exit 2
fi
done
exit 0
Hazlo ejecutable con chmod +x .claude/hooks/protect-files.sh y regístralo:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}
El mismo patrón sirve para comandos de shell. Haz match con Bash, lee .tool_input.command y sal con 2 cuando contenga algo que nunca quieres, como rm -rf /, git push --force o DROP TABLE. Ten en cuenta que una comprobación de cadenas es fácil de esquivar, así que trátala como un cinturón de seguridad, no como un cerrojo.
Ejemplo 3: recibir una notificación de escritorio
Las tareas largas se detienen a esperarte más a menudo de lo que te gustaría. Un hook Notification te avisa al instante. En macOS:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}
En Linux, usa en su lugar notify-send 'Claude Code' 'Claude Code needs your attention'. Pon el matcher en permission_prompt si solo te importan las peticiones de permiso, o añade un hook Stop con el mismo comando para enterarte cuando Claude termine.
Más patrones útiles
- Reinyectar contexto tras la compactación. Un hook
SessionStartcon el matchercompactpuede hacerechode un recordatorio corto de tus reglas. Su stdout se añade al contexto nuevo. - Auditar cambios de configuración. Un hook
ConfigChangepuede añadir cada cambio de ajustes a un archivo de log. - Control de calidad en Stop. Un hook
Stoppuede ejecutar tus tests y salir con 2 con los fallos, para que Claude siga corrigiendo. Comprueba el campostop_hook_activede la entrada para evitar un bucle infinito. Claude Code también anula un hook Stop tras varios bloqueos seguidos. - Autoaprobar un prompt seguro. Un hook
PermissionRequestpuede responder a un diálogo concreto con{"behavior": "allow"}dentro de su objeto de decisión. Mantén el matcher estrecho.
Notas de seguridad antes de añadir hooks
Los hooks se ejecutan como comandos de shell con los permisos de tu usuario, sin preguntar. Ese es el objetivo, y también el riesgo.
- Lee los hooks antes de fiarte de un repositorio. El
.claude/settings.jsonde un proyecto puede incluir hooks. Revísalos como revisarías un scriptpostinstall. - Pon comillas a tus variables. La entrada del hook viene del modelo, y el modelo lee archivos y páginas web que pueden contener inyección de prompts. Usa
"$VAR", nunca hagas eval de la entrada y prefierejqa un análisis hecho a mano. - Usa rutas absolutas o relativas al proyecto con
$CLAUDE_PROJECT_DIR, para que un hook no dependa de la carpeta actual. - Mantén los hooks rápidos. Un hook
PreToolUselento ralentiza cada llamada a una herramienta. Define untimeout. - Los hooks no son un sandbox. Un hook deny bloquea llamadas incluso con
--dangerously-skip-permissions, lo cual es útil, pero un agente decidido o confundido puede llegar al mismo resultado por otro camino. Combina los hooks con reglas deny y aislamiento, como se explica en seguridad de los agentes de programación con IA y saltarse los permisos con seguridad. - Conoce los interruptores de apagado.
"disableAllHooks": truedesactiva los hooks, y los administradores pueden definirallowManagedHooksOnlyen los ajustes gestionados para bloquear los hooks de usuario y de proyecto.
Cuando un hook se porta mal, inicia Claude Code con claude --debug, o escribe el log en un archivo con --debug-file /tmp/claude.log, para ver el matcher, el código de salida y la salida de cada hook.
Cómo usa AgentVera los hooks
AgentVera es una app de escritorio que ejecuta Claude Code, Codex, Gemini CLI y otras CLI de programación lado a lado. Los hooks son una parte importante de cómo trabaja con Claude Code.
- Tus hooks siguen siendo tuyos. AgentVera pasa sus propios hooks a cada proceso de Claude Code con el flag
--settings. Claude Code los combina con los hooks de tus archivos de ajustes, y tusettings.jsonno se edita. - El estado de los agentes viene de los hooks. El estado que muestra cada agente en la app se lee de los propios eventos de hook de la CLI, no se adivina a partir del texto de la terminal.
- Las smart tools son hooks. La función de smart tools añade cuatro hooks a los agentes locales de Claude Code: bloquea las lecturas repetidas del mismo archivo, ejecuta solo los tests que afecta una edición, comprueba errores justo después de una edición y comparte resultados entre agentes. Están activadas por defecto y puedes desactivarlas en Settings → “Token saving”.
- No hace falta un script de avisos. Notificaciones y aprobaciones reúne en una sola cola a todos los agentes que esperan aprobación, envía notificaciones de escritorio mientras la app está en segundo plano y puede avisar a tu teléfono por Telegram.
Qwen Code también recibe los hooks de AgentVera, porque su sistema de hooks sigue los eventos y el formato JSON de Claude Code.
Para terminar
Empieza con un formateador PostToolUse y una barrera PreToolUse, compruébalos con /hooks y --debug, y añade más solo cuando una regla se siga incumpliendo. Sal con 2 para bloquear, imprime JSON cuando necesites más control y trata cada hook como código que se ejecuta con tus permisos.
Para seguir leyendo:
Para probar AgentVera, empieza en la página de inicio.