Claude Code hooks. Cómo configurarlos paso a paso

Configura los hooks de Claude Code paso a paso, con la tabla de eventos, el matcher explicado y seis bloques JSON listos para copiar en tu settings.json.

Los hooks de Claude Code son comandos que el propio programa ejecuta en momentos concretos de la sesión, al margen de lo que le pidas en el prompt. Se configuran con un bloque hooks en tu settings.json, donde eliges el evento y el comando.

Con unas pocas líneas de JSON consigues que Claude formatee lo que edita o que no pueda tocar tu .env, el archivo donde guardas tus claves. Al terminar tendrás tu primer hook funcionando y varios bloques listos para pegar, así que ve siguiendo los pasos en tu propio proyecto.

Un hook se ejecuta siempre, aunque no se lo pidas a Claude

Cuando le pides algo en el prompt, como que pase los tests antes de terminar, esa instrucción compite con todo lo demás que hay en el contexto y puede caerse por el camino.

Un hook lo ejecuta el propio programa en cuanto se cumple el evento. Las skills y los comandos personalizados no funcionan así, porque una skill entra cuando Claude cree que viene a cuento y un comando solo si lo escribes tú.

Compensa convertir en hook lo que quieres que ocurra sin excepciones, como el formateo, las validaciones o los bloqueos sobre archivos delicados. Lo que depende del contexto vive mejor en el prompt.

Así funcionan los hooks de Claude Code por dentro

Un hook son tres piezas en el mismo bloque JSON, el evento que lo dispara, el matcher que filtra y la acción que se ejecuta. El evento es el momento en el que Claude Code mira si tienes algo registrado, así que elígelo según cuándo quieras que actúe, antes o después de la acción, sobre una herramienta o sobre el turno entero.

El matcher decide a qué llamadas de ese evento responde el hook. En los eventos de herramienta se compara con el nombre de la herramienta, así que Edit|Write deja pasar solo las ediciones de archivo. Si lo dejas vacío salta siempre. Para un aviso viene bien, pero uno que bloquea frenaría cada llamada, incluso las que no pintan nada.

La acción no siempre es un comando de shell. Con command lanzas la línea que escribirías tú en el terminal, con http mandas los datos del evento a una URL, con mcp_tool llamas a una herramienta de un servidor MCP, y prompt y agent le pasan la decisión a un modelo.

Al dispararse, Claude Code le entrega al hook un JSON por stdin, la entrada estándar del comando, con la herramienta y lo que va a hacer. El hook responde con un número, su código de salida, y ahí el 0 no pone pegas y el 2 frena la acción en los eventos que admiten bloqueo, con el motivo que le escribas a Claude por stderr.

La lista completa pasa de treinta eventos, con entradas hasta para los cambios de configuración y las tareas que Claude lanza en segundo plano. La tienes entera en la referencia de hooks de Anthropic, y esta tabla recoge diez de los más frecuentes.

Tabla de diez eventos de hooks de Claude Code con cuándo se dispara cada uno y su uso típico

Monta tu primer hook de principio a fin

Vas a montar un hook que te avisa en el escritorio cuando Claude se queda esperando tu respuesta. Necesitas Claude Code funcionando en tu terminal y un proyecto abierto donde probar. Son seis pasos que empiezan por elegir el evento y terminan afinando el matcher, así que hazlos en orden.

Decide qué tarea vas a automatizar y con qué evento

El aviso que buscas llega cuando Claude necesita que apruebes algo o cuando termina y se queda esperando, que es lo que cubre el evento Notification. Los demás eventos de la tabla se disparan por algo que Claude hace, no porque te esté esperando.

Elige el archivo de configuración según el alcance

Donde escribas el bloque decide a qué proyectos llega el hook y si tu equipo se lo encuentra. Tienes tres sitios donde ponerlo.

  • ~/.claude/settings.json, para todos tus proyectos y sin salir de tu máquina.
  • .claude/settings.json, para un proyecto concreto y compartible por git con el equipo.
  • .claude/settings.local.json, para un proyecto concreto y en local, ya que Claude Code lo crea ignorado por git.

El aviso de escritorio solo te interesa a ti, por eso este primer hook va en el settings.json de tu carpeta personal, y si no existe, créalo. Las normas del proyecto, como el formateo, van en el .claude/settings.json, que es el que se sube a git.

Escribe el bloque de hooks

Abre el archivo y pega esto dentro, con el evento como primera clave. El comando de ejemplo usa osascript, el intérprete de AppleScript que trae macOS.

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code te espera\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

En Linux la misma línea se escribe con notify-send 'Claude Code' 'Te espera'. En Windows hace falta antes el módulo BurntToast, que instalas con Install-Module BurntToast. Con él ya puedes usar powershell -Command "New-BurntToastNotification -Text 'Claude Code te espera'".

El anidamiento tiene tres niveles y es donde suele fallar la primera vez. Cada nombre de evento contiene una lista de grupos, cada grupo tiene su matcher y su array hooks, y dentro van los comandos.

Si ya tenías un bloque hooks, añade Notification y conserva el resto. Ojo con las comillas dobles del comando, que dentro del JSON van escapadas con \", y guarda sin comas finales.

Comprueba que ha quedado registrado

Vuelve a la sesión y escribe /hooks. Se abre un listado de eventos con un contador en cada uno que tenga algo configurado, y al entrar en Notification verás tu hook con su matcher y su comando.

El listado es de solo lectura, así que los cambios los sigues haciendo en el JSON, que Claude Code recoge en caliente en unos segundos. Si pasado ese rato tu hook no sale, reinicia la sesión.

Provoca el evento para probarlo

Pulsa Esc para salir del menú y pídele a Claude algo que necesite tu permiso, por ejemplo que ejecute un comando en el terminal. Cambia de ventana y deberías ver el aviso en el escritorio.

En macOS puede que la primera vez no salga nada, porque osascript avisa a través del Editor de Scripts y esa app necesita permiso. Actívalo en Ajustes del Sistema, Notificaciones, Editor de Scripts.

Afina el matcher para que no salte de más

Para que el aviso solo suene cuando Claude pide permiso, cambia el matcher a permission_prompt, y para enterarte de cuándo ha terminado, a idle_prompt.

Los matchers distinguen mayúsculas de minúsculas, y un valor mal escrito deja el hook sin saltar nunca. Guarda, repite la prueba y comprueba que ya solo salta en el caso que has elegido.

Hooks listos para copiar en tu configuración

Estos bloques funcionan tal cual, aunque conviene leerlos antes de pegarlos porque un hook se ejecuta con tus permisos y sin pedir confirmación. Varios necesitan jq, que lee JSON desde el terminal, con brew install jq en macOS, apt-get install jq en Debian y Ubuntu o winget install jqlang.jq en Windows.

Formatea el código después de cada edición

Este hook coge el archivo que Claude acaba de editar y lo pasa por Prettier, que lo devuelve con el formato del proyecto. Va en PostToolUse con matcher Edit|Write, así que solo actúa sobre las herramientas de edición. Si Claude crea o cambia un archivo ejecutando un comando en el terminal, ese no pasa por aquí.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

Guárdalo en el .claude/settings.json del proyecto, que es donde tiene sentido una norma de formato compartida. Desde la versión 2.1.191 de Claude Code el matcher también se puede escribir como Edit,Write.

Blinda los archivos que Claude no debe tocar

Este ejemplo va en dos piezas, un script que revisa la ruta antes de cada edición y el bloque que lo engancha en PreToolUse con matcher Edit|Write. Frena la operación cuando el archivo coincide con algo protegido, como tu .env, el package-lock.json o lo que haya dentro de .git/, y Claude recibe el motivo por stderr.

#!/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 "Bloqueado: $FILE_PATH coincide con el patrón protegido '$pattern'" >&2
    exit 2
  fi
done

exit 0

Guarda el script en .claude/hooks/protect-files.sh y dale permisos con chmod +x, un paso que se olvida a menudo y sin el que el hook no se ejecuta. Es un script de shell, así que en Windows lo lanzas desde Git Bash o WSL. El bloque de abajo es el que lo engancha, y va en el .claude/settings.json del proyecto.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"${CLAUDE_PROJECT_DIR}\"/.claude/hooks/protect-files.sh"
          }
        ]
      }
    ]
  }
}

Registra cada comando que Claude ejecuta

Todo lo que pasa por la herramienta Bash queda guardado en un archivo. Al ir en PostToolUse, el hook solo anota lo que llegó a ejecutarse.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt"
          }
        ]
      }
    ]
  }
}

Si quieres que el registro recoja también lo que se frenó, pon en PreToolUse este hook y el que bloquea, porque entre hooks del mismo evento gana la respuesta más restrictiva.

Recupera el contexto cuando Claude compacta la conversación

Al llenarse la ventana, Claude Code resume lo hablado para hacer sitio y por el camino pierde detalles. Un hook SessionStart con matcher compact vuelve a meter lo esencial justo después.

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Recuerda que este proyecto usa Bun. Pasa bun test antes de cada commit.'"
          }
        ]
      }
    ]
  }
}

Todo lo que el comando escriba en stdout, su salida normal, se añade al contexto, y el echo admite cualquier salida, como git log --oneline -5. Si lo que quieres es el mismo contexto en todas las sesiones, escríbelo en el archivo CLAUDE.md.

Dispara un webhook en Make cuando Claude termina

Un webhook es una URL que espera datos, y con el tipo http Claude Code se los manda por POST en cuanto salta el evento. La lógica está en el otro extremo, en Slack o en un escenario de Make que arranca con un módulo Webhooks.

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "http",
            "url": "https://hook.eu2.make.com/tu-webhook"
          }
        ]
      }
    ]
  }
}

Stop salta cada vez que Claude termina de responder, incluso con la tarea a medias, y en una conversación larga el webhook se dispara muchas más veces de las que esperas.

Comprueba que los tests pasan antes de dar el turno por terminado

Saber si los tests pasan no es cosa de buscar una palabra en la pantalla, hay que leer el resultado y decidir, así que usa un hook de tipo agent en Stop. Lanza un subagente que ejecuta la suite y decide, y cuando algo falla devuelve el motivo para que Claude siga trabajando hasta arreglarlo.

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "Comprueba que todos los tests unitarios pasan. Ejecuta la suite y revisa los resultados. $ARGUMENTS",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

Los hooks de agente son experimentales, y para algo crítico conviene un hook de comando. Claude Code corta además un hook Stop después de ocho bloqueos seguidos sin avance.

Comprueba esto cuando un hook no salta

Si tu hook no sale en /hooks ni después de reiniciar, es que Claude Code no ha llegado a leerlo. Estas son las causas que solemos ver.

  • Un JSON inválido, porque las comas finales y los comentarios están prohibidos y con un solo fallo Claude Code no llega a cargar tus hooks.
  • El bloque escrito en un settings.json que no es el de este proyecto ni el de tu carpeta personal, como el de otro repositorio que abriste antes.
  • Un hooks anidado dentro de otra clave, cuando va al primer nivel del archivo, que es donde Claude Code lo busca.

Si aparece pero no hace lo suyo, prueba su comando por tu cuenta, sea una línea suelta o un script. Le pasas por stdin un JSON de ejemplo y miras el código de salida.

echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./mi-hook.sh
echo $?

Un command not found se arregla con rutas absolutas o con ${CLAUDE_PROJECT_DIR}, y si el script ni arranca, dale un chmod +x. Para el resto, el registro completo sale al arrancar con claude --debug-file /tmp/claude.log, y la transcripción con Ctrl+O te resume cada hook que saltó.

Los hooks de Claude Code convierten en norma del proyecto lo que antes dependía de que te acordaras de pedirlo. Con el evento bien elegido y el matcher afinado cubres casi todo lo que se automatiza a diario.

Si quieres seguir sacándole partido a la herramienta, en el curso gratuito de Vibe Coding con Claude Code construyes una app entera hablando con el terminal.