Guía

Hooks de Claude Code: cómo convertir las reglas del agente en garantías

Actualizado: · Skillaria.Top

Los hooks de Claude Code son comandos de shell que el propio Claude Code ejecuta en determinados eventos: antes y después de llamar a una herramienta, al enviar un prompt, al iniciar la sesión y antes de que el agente se detenga. Se configuran en settings.json, y el código de salida 2 bloquea la acción y devuelve al agente un mensaje de error; así el hook convierte una regla de CLAUDE.md en una garantía que el modelo no puede olvidar.

Qué son los hooks de Claude Code

Un hook es un comando que no lanza el modelo, sino el propio programa Claude Code en el momento concreto del trabajo del agente. El agente está a punto de ejecutar rm -rf: el hook se activa y decide si deja pasar el comando. El agente ha editado un archivo: el hook ejecuta el formateador. El agente quiere terminar: el hook lanza las pruebas y no le deja parar mientras estén en rojo.

La diferencia principal con las instrucciones de CLAUDE.md es esta: una instrucción es un texto que el modelo tiene en cuenta con cierta probabilidad, mientras que un hook es código determinista. Se ejecuta siempre, independientemente de la longitud del contexto, de lo que el agente haya «decidido» o de si olvidó la regla en el minuto cien de la sesión. Por eso los hooks de Claude Code son la forma principal de convertir un deseo en una garantía.

Por qué las reglas en CLAUDE.md no bastan

Una situación típica: el agente terminó una función, las pruebas unitarias están en verde y en el chat aparece un seguro «Listo». Pero nadie ejecutó el escenario de extremo a extremo, el linter no se pasó y la migración falló a mitad. En CLAUDE.md, sin embargo, está escrito «antes de terminar, ejecuta las pruebas». El modelo no engaña a propósito: simplemente se evalúa a sí mismo, y esa autoevaluación no es fiable.

De ahí el principio del harness: todo lo que se puede comprobar mecánicamente lo comprueba la mecánica, no el modelo. La regla «ejecuta el linter» se convierte en un hook que ejecuta el linter. La regla «no toques la configuración de producción» se convierte en un hook que bloquea la escritura en ese archivo. La instrucción de texto se mantiene, pero solo explica al agente por qué lo detuvieron.

Eventos: cuándo se activan los hooks

Cada hook está ligado a un evento del ciclo de vida de la sesión. Los principales eventos:

EventoCuándo se activaUso típico
PreToolUseantes de llamar a una herramienta (Bash, Edit, Write, etc.)bloquear comandos peligrosos, proteger archivos
PostToolUsedespués de una llamada correcta a una herramientaformateador, linter, pruebas rápidas tras una edición
UserPromptSubmitcuando el usuario envía un prompt, antes de procesarloañadir contexto, rechazar un prompt con un secreto
Stopcuando el agente está a punto de terminar la respuestano dejarlo parar mientras no pasen las comprobaciones
SubagentStopcuando termina un subagenterevisar el resultado del subagente
SessionStartal iniciar o reanudar una sesióncargar el estado: git status, el plan, el diario de progreso
SessionEndal terminar la sesiónlimpieza, escritura de registros
Notificationcuando Claude Code envía una notificación (por ejemplo, espera un permiso)sonido o mensaje en el teléfono
PreCompactantes de comprimir el contextoguardar lo importante antes de la compactación

Los más útiles para la fiabilidad son PreToolUse, PostToolUse y Stop: cubren tres puntos débiles del agente: las acciones peligrosas, las ediciones sin comprobar y el «listo» prematuro.

Dónde se configuran los hooks: settings.json

Los hooks se describen en los archivos de configuración de Claude Code:

  • ~/.claude/settings.json: personales, para todos los proyectos;
  • .claude/settings.json en el repositorio: compartidos por el equipo, se guardan en git;
  • .claude/settings.local.json: locales del proyecto, no entran en git.

La estructura es: evento → lista de grupos con matcher → lista de comandos.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/lint.sh",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

matcher es el nombre de una herramienta o una expresión regular sobre ese nombre: Bash, Edit|Write, .*. Hace falta en los eventos relacionados con herramientas; para Stop o UserPromptSubmit puede omitirse. La variable CLAUDE_PROJECT_DIR apunta a la raíz del proyecto, así que conviene guardar los scripts en .claude/hooks/ y llamarlos a través de ella. Para ver los hooks activos, use el comando /hooks dentro de Claude Code.

Cómo el hook recibe los datos y responde

Entrada: JSON por stdin

Claude Code pasa al hook un JSON con los datos del evento: el identificador de la sesión, el directorio de trabajo, el nombre del evento y, para las herramientas, tool_name y tool_input. En Bash, tool_input.command contiene el comando; en Edit y Write, tool_input.file_path contiene la ruta del archivo. Es cómodo leerlo con jq.

{
  "session_id": "abc123",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "rm -rf build/" },
  "cwd": "/home/dev/project"
}

Salida: código de retorno

CódigoQué ocurre
0éxito, el trabajo continúa; en UserPromptSubmit y SessionStart la salida stdout se añade al contexto del agente
2error bloqueante: stderr se entrega al agente y la acción se detiene (en PreToolUse, la llamada a la herramienta se cancela; en Stop, el agente sigue trabajando)
otroerror no bloqueante: stderr se muestra al usuario y el trabajo continúa

El código 2 es la mecánica clave. El agente recibe el texto de stderr como retroalimentación y puede corregirse: reescribir el comando, arreglar la prueba, no tocar el archivo. Por eso en stderr conviene escribir no «error», sino el motivo claro y qué hacer en su lugar.

Además del código de retorno, el hook puede imprimir en stdout un JSON con la decisión, por ejemplo "decision": "block" y "reason". Sirve para ajustes finos, pero para la mayoría de tareas basta con el código 2 y un mensaje en stderr.

Ejemplo 1: linter y pruebas tras cada edición

Un hook en PostToolUse con el matcher Edit|Write se activa después de modificar un archivo. La herramienta ya se ejecutó y la edición no se puede deshacer, pero el código 2 devuelve al agente la salida del linter, y lo corrige en ese momento, no dentro de media hora.

#!/usr/bin/env bash
# .claude/hooks/lint.sh
file=$(jq -r '.tool_input.file_path // empty')
case "$file" in
  *.py)
    if ! out=$(ruff check "$file" 2>&1); then
      echo "El linter encontró errores en $file:" >&2
      echo "$out" >&2
      exit 2
    fi ;;
  *.php)
    if ! out=$(php -l "$file" 2>&1); then
      echo "$out" >&2
      exit 2
    fi ;;
esac
exit 0

No ponga aquí una ejecución completa de las pruebas de diez minutos: el hook se activa en cada edición. Tras una edición, use comprobaciones rápidas (sintaxis, linter, pruebas de un módulo), y las pesadas déjelas para el hook Stop.

Ejemplo 2: bloqueo de comandos peligrosos

Un hook en PreToolUse con el matcher Bash ve el comando antes de ejecutarlo. El código 2 cancela la llamada y el agente recibe la explicación.

#!/usr/bin/env bash
# .claude/hooks/guard.sh
cmd=$(jq -r '.tool_input.command // empty')
if echo "$cmd" | grep -Eq 'rm -rf /|git push (-f|--force)|DROP (TABLE|DATABASE)'; then
  echo "Comando bloqueado por la política del proyecto: $cmd. Las operaciones peligrosas las ejecuta una persona." >&2
  exit 2
fi
exit 0

Así también se protegen los archivos: un hook en Edit|Write revisa file_path y bloquea la escritura en .env, en configuraciones de producción, en código generado o en migraciones ya aplicadas. Tenga en cuenta que una búsqueda por expresión regular se puede esquivar: el agente puede escribir el comando de otra forma. El hook es un buen seguro, pero no sustituye a los permisos de acceso ni al sandbox.

Ejemplo 3: el agente no puede decir «listo» mientras las pruebas estén en rojo

El hook Stop se activa cuando el agente está a punto de terminar. Si las pruebas no pasan, el código 2 impide que se detenga, y la salida de las pruebas le llega al agente como la siguiente tarea. Es la versión mecánica de la Definition of Done.

#!/usr/bin/env bash
# .claude/hooks/done.sh
input=$(cat)
# protección contra bucles infinitos: el hook ya prolongó el trabajo una vez
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0
fi
if ! out=$(npm test --silent 2>&1); then
  echo "Las pruebas no pasaron, el trabajo no está terminado:" >&2
  echo "$out" | tail -n 40 >&2
  exit 2
fi
exit 0

El campo stop_hook_active vale true si el agente ya continúa trabajando por un hook de parada. Sin esta comprobación, el hook puede hacer que el agente dé vueltas sin fin: justo la situación en la que el agente «trabaja toda la noche en la dirección equivocada». En los ciclos autónomos añada además un límite de intentos y una condición en la que se avise a una persona.

Ejemplo 4: cargar el estado al inicio de la sesión

En SessionStart, la salida stdout del hook llega al contexto. Es cómodo mostrar la rama actual, los cambios sin confirmar y la siguiente tarea del plan, para que el agente no empiece desde cero.

#!/usr/bin/env bash
echo "Rama: $(git branch --show-current)"
git status --short | head -n 20
[ -f PROGRESS.md ] && tail -n 15 PROGRESS.md
exit 0

Cómo los hooks convierten las reglas en garantías

Conviene dividir las reglas del harness en tres niveles. El primero es el texto en CLAUDE.md: explica cómo trabajar, pero se cumple con probabilidad. El segundo son las comprobaciones ejecutables: pruebas, E2E, reglas de arquitectura en forma de script que falla si hay una infracción. El tercero son los hooks, que lanzan esas comprobaciones automáticamente en el momento oportuno, sin intervención del modelo.

Regla práctica: si el agente infringió dos veces la misma regla, no añada otra advertencia en mayúsculas a CLAUDE.md. Pregúntese si la infracción se puede verificar con un script. Si se puede, cree el script y engánchelo a un hook. Así la instrucción de texto se hace más corta y el comportamiento más estable.

Regla en el textoGarantía mediante hook
«Formatea el código según el estándar»PostToolUse ejecuta el formateador sobre el archivo modificado
«No hagas push con force»PreToolUse en Bash bloquea el comando, exit 2
«No digas listo mientras las pruebas estén en rojo»Stop ejecuta las pruebas y no deja terminar
«No edites .env»PreToolUse en Edit|Write comprueba la ruta
«Recuerda en qué te quedaste»SessionStart muestra el plan y git status

Errores típicos

  • Código 1 en lugar de 2. El script termina con código 1, el agente no se entera y la acción sigue adelante. Solo el código 2 bloquea.
  • Mensaje en stdout. Con código 2, al agente le llega stderr. Escriba con >&2.
  • Hook lento en un evento frecuente. Una ejecución completa de las pruebas en PostToolUse hace que cada edición tarde un minuto. Lo pesado va en Stop.
  • Stop sin fin. Sin comprobación de stop_hook_active ni límite, el agente da vueltas sin parar.
  • Rutas relativas. El hook se ejecuta en el directorio de trabajo de la sesión; use $CLAUDE_PROJECT_DIR.
  • Hook ajeno sin revisar. El hook se ejecuta con sus permisos y sin confirmación. Lea los scripts de repositorios ajenos antes de confiar en ellos.

Resumen

Los hooks de Claude Code trasladan las reglas más importantes del texto al código: PreToolUse detiene las acciones peligrosas, PostToolUse revisa cada edición, Stop impide informar de que todo está listo antes de tiempo y SessionStart devuelve al agente la memoria del proyecto. Empiece con dos o tres hooks para las reglas que el agente ya ha infringido y escriba los mensajes de stderr de forma clara: así el agente corrige sus errores por sí mismo y usted revisa el resultado, no el proceso. Las validaciones, la comprobación de la finalización y los ciclos autónomos los explicamos en la práctica dentro del curso.

Preguntas frecuentes

¿Hace falta reiniciar Claude Code después de cambiar los hooks?

Los cambios en settings.json se aplican con más seguridad en una sesión nueva; la configuración de hooks vigente se puede ver con el comando /hooks.

¿Los hooks tienen que escribirse en bash?

No. Un hook es cualquier comando: un script en Python, Node.js o un binario. Lo único importante es leer el JSON de stdin y devolver el código de salida correcto.

¿El agente ve la salida de un hook que termina bien?

Normalmente no: con código 0 la salida no llega al contexto. Las excepciones son UserPromptSubmit y SessionStart, donde stdout se añade al contexto del agente.

¿Los hooks funcionan también para los subagentes?

Sí. Las llamadas a herramientas de los subagentes pasan por los mismos hooks PreToolUse y PostToolUse, y para su finalización existe el evento específico SubagentStop.

¿Es seguro guardar los hooks en el repositorio?

Sí. El archivo .claude/settings.json y los scripts de .claude/hooks/ son práctica habitual en un equipo; los hooks personales conviene guardarlos en settings.local.json o en ~/.claude/settings.json.