Hooks de Claude Code: guía práctica con ejemplos

Cómo funcionan los hooks de Claude Code: eventos, settings.json, matchers, códigos de salida y JSON, con ejemplos para formatear, bloquear comandos y avisarte.

Solviera Teknoloji 12 min de lectura

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 .env o ejecutar rm -rf en 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:

EventoCuándo se dispara¿Puede bloquear?
PreToolUseAntes de ejecutar una llamada a una herramientaSí, bloquea la llamada
PostToolUseDespués de que una llamada a una herramienta termina bienNo, la herramienta ya se ejecutó; stderr llega a Claude
UserPromptSubmitCuando envías un promptSí, el prompt nunca llega a Claude
NotificationCuando Claude Code envía una notificación, como una petición de permiso o una espera por inactividadNo
StopCuando Claude termina su respuestaSí, Claude sigue trabajando
SubagentStart / SubagentStopCuando un subagente empieza o terminaSolo SubagentStop
SessionStartAl arrancar, reanudar, con /clear o al compactarNo, pero stdout pasa a ser contexto
PreCompactAntes de compactar el contextoSí, bloquea la compactación
PermissionRequestCuando está a punto de mostrarse un diálogo de permisosSolo mediante JSON, no con exit 2
SessionEndCuando termina la sesiónNo

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:

ArchivoAlcance¿Se comparte con git?
~/.claude/settings.jsonTodos tus proyectosNo
.claude/settings.jsonUn proyectoSí
.claude/settings.local.jsonUn proyecto, solo túNo (en gitignore)
Ajustes de política gestionadaToda la organizaciónLos 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:

MatcherSignificado
"*", "" o sin matcherTodas 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 salidaQué 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.
2Error bloqueante. stderr es el motivo. En PreToolUse se cancela la llamada, en UserPromptSubmit se rechaza el prompt, en Stop Claude sigue.
Cualquier otro, incluido 1Error 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 SessionStart con el matcher compact puede hacer echo de un recordatorio corto de tus reglas. Su stdout se añade al contexto nuevo.
  • Auditar cambios de configuración. Un hook ConfigChange puede añadir cada cambio de ajustes a un archivo de log.
  • Control de calidad en Stop. Un hook Stop puede ejecutar tus tests y salir con 2 con los fallos, para que Claude siga corrigiendo. Comprueba el campo stop_hook_active de 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 PermissionRequest puede 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.json de un proyecto puede incluir hooks. Revísalos como revisarías un script postinstall.
  • 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 prefiere jq a 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 PreToolUse lento ralentiza cada llamada a una herramienta. Define un timeout.
  • 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": true desactiva los hooks, y los administradores pueden definir allowManagedHooksOnly en 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 tu settings.json no 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.

Preguntas frecuentes

¿Qué son los hooks de Claude Code?

Los hooks son comandos de shell, llamadas HTTP, herramientas MCP o prompts que Claude Code ejecuta por sí mismo en puntos concretos de una sesión, como antes de llamar a una herramienta, después de editar un archivo o cuando Claude necesita tu atención. Los defines en settings.json.

¿Dónde pongo los hooks de Claude Code?

En la clave hooks de un archivo de ajustes: ~/.claude/settings.json para todos tus proyectos, .claude/settings.json para un proyecto que compartes con git o .claude/settings.local.json para un proyecto solo en tu máquina. Los plugins, las skills y los subagentes también pueden definir hooks.

¿Cómo bloquea un hook una llamada a una herramienta?

Un hook PreToolUse sale con código 2 y escribe el motivo en stderr, o sale con 0 e imprime JSON con permissionDecision en deny. Claude Code cancela la llamada y le muestra el motivo a Claude. El código de salida 1 no bloquea.

¿Se ejecutan los hooks con --dangerously-skip-permissions?

Sí. Un hook que devuelve deny bloquea la llamada a la herramienta incluso en modo bypassPermissions. Eso convierte a un hook PreToolUse en una última barrera útil para comandos que nunca quieres, aunque no es un sandbox.

¿Cómo depuro un hook de Claude Code que no se dispara?

Abre /hooks para ver qué hooks están cargados y de dónde vienen, comprueba que el matcher coincide con el nombre exacto de la herramienta e inicia Claude Code con --debug o --debug-file para ver cada ejecución de hook, su código de salida y su salida.

Reúne a tus agentes en un mismo escritorio.

Descarga AgentVera gratis; tus CLI instaladas ya están listas.

Más artículos