Image for post Hooks en Claude Code: checks automáticos sin tocar el flujo

Hooks en Claude Code: checks automáticos sin tocar el flujo


TL;DR

  • Los hooks de Claude Code son comandos shell que se disparan en eventos del agente (antes o después de usar una tool, al recibir un prompt, al cerrar sesión).
  • Permiten correr linters, formateadores, validaciones o bloqueos sin meter ruido en el prompt.
  • Se configuran en settings.json a nivel proyecto o global y conviven con permisos y MCPs.

Por qué los hooks importan en tu flujo diario

Cuando trabajo con Claude Code en un repo real, hay tareas que repito en cada sesión: pasar el formateador después de un Edit, validar que no se cuele un console.log, o evitar que el agente ejecute comandos destructivos sobre node_modules. Antes lo hacía recordándoselo en el prompt, lo cual es frágil: si la sesión se compacta o cambio de modelo, el contexto se pierde.

Los hooks resuelven esto moviendo esos chequeos fuera del prompt: el harness los ejecuta como código, no como instrucción al modelo. Es la diferencia entre pedirle a un compañero que recuerde formatear el código y tener un pre-commit hook que lo hace siempre.

Si vienes de configurar memoria y permisos, esto encaja en la misma capa de setup serio que ya cubrí en Claude Code: Memoria, MCPs y Mapa de Repo para Menos Tokens.

¿Qué es un hook en Claude Code?

Un hook es un comando shell que se ejecuta automáticamente cuando ocurre un evento del agente. Recibe información del evento por stdin en formato JSON, puede modificar el comportamiento (bloquear, advertir, inyectar contexto) y devuelve un exit code que decide si la acción continúa.

Es código tuyo corriendo en tu máquina, no parte del prompt. El modelo no lo ve a menos que tú decidas devolverle algo por stdout.

Eventos disponibles (los que uso de verdad)

EventoCuándo disparaCaso típico
PreToolUseAntes de ejecutar una tool (Bash, Edit, Write...)Bloquear comandos peligrosos, validar paths
PostToolUseDespués de ejecutar una toolFormatear código tras Edit, correr linter
UserPromptSubmitCuando envías un mensaje al agenteInyectar contexto del repo, normalizar prompts
StopCuando el agente termina su respuestaResumen de cambios, notificación
SessionStartAl abrir sesiónCargar variables, mostrar estado del repo

Hay más, pero estos cinco cubren el 90% de lo que vas a querer hacer.

Configuración paso a paso

1. Localiza tu settings.json

Tienes dos niveles:

  • Global: ~/.claude/settings.json, aplica a todas las sesiones.
  • Proyecto: .claude/settings.json en la raíz del repo, versionable.

Para hooks que dependen del stack del proyecto (Prettier, ESLint, Black, Ruff...), usa el del proyecto. Para reglas de seguridad personales, el global.

2. Define tu primer hook: formateo automático tras editar

Este hook lanza Prettier sobre cada archivo que el agente edita o crea, sin que tú lo pidas.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs -I {} npx prettier --write {} 2>/dev/null || true"
          }
        ]
      }
    ]
  }
}

Qué hace: lee el JSON del evento por stdin, extrae la ruta del archivo y lanza Prettier. El || true evita que un fallo del formateador rompa el flujo del agente.

3. Añade un guardrail con PreToolUse

Bloquea cualquier rm -rf antes de que se ejecute. Devolver exit code 2 en un PreToolUse cancela la acción y devuelve el mensaje al agente.

#!/usr/bin/env bash
# Bloquea rm -rf en cualquier path; devuelve mensaje al agente
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command // ""')
if echo "$cmd" | grep -qE 'rm\s+-rf'; then
  echo "Bloqueado: rm -rf no permitido. Usa trash o confirma manualmente." >&2
  exit 2
fi
exit 0

Guarda el script como .claude/hooks/block-rm.sh, dale permisos (chmod +x) y referénciálo desde settings.json con matcher: "Bash".

4. Verifica que dispara

Pídele al agente algo trivial ("edita el README y añade una línea") y observa la consola. Si Prettier corrió, verás el archivo formateado al instante. Si no, revisa el siguiente apartado de errores.

Caso real: linting silencioso en un repo Python

En un proyecto FastAPI tenía dos problemas: el agente generaba imports desordenados y a veces dejaba print() de debug. La solución fue un PostToolUse con dos comandos encadenados:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | grep '\\.py$' | xargs -I {} sh -c 'ruff check --fix {} && ruff format {}' 2>/dev/null || true"
          }
        ]
      }
    ]
  }
}

Resultado: el código sale del agente ya formateado y con imports ordenados. Sin recordatorios en el prompt, sin pasos manuales. Es el mismo principio que aplico cuando uso slash commands en Claude Code para automatizar tareas: bajar el coste cognitivo de cada sesión.

En Producción

Rendimiento y timeouts

Cada hook bloquea el flujo del agente hasta que termina. Si tu linter tarda 8 segundos, vas a notarlo en cada Edit. Reglas que aplico:

  • Hooks incrementales: corre Prettier o Ruff solo sobre el archivo afectado, no sobre todo el repo.
  • Timeout explícito: envuelve comandos lentos en timeout 5s ... para evitar bloqueos largos.
  • Evita tests completos en PostToolUse. Para eso, mejor un Stop que corre una vez al final de la respuesta.

Costes

Los hooks no consumen tokens del modelo (es código local), pero si devuelves contenido al agente vía stdout en eventos como UserPromptSubmit o PreToolUse, ese texto sí entra al contexto. Mantén las salidas cortas y verifica que no estás duplicando información que ya está en CLAUDE.md.

Seguridad

Un settings.json en el repo puede ejecutar comandos arbitrarios al abrir el proyecto. Si clonas un repo desconocido, revisa .claude/ antes de abrir Claude Code. Es el mismo riesgo que con scripts de Husky o pre-commit. La capa de protección complementa lo que cubrí en guardrails en Claude Code.

Versionado

El .claude/settings.json del proyecto debería ir al repo para que el equipo comparta los mismos hooks, igual que se versiona un .eslintrc. El global queda en tu máquina. No mezcles secretos en hooks: usa variables de entorno.

Errores comunes

Error: el hook no dispara nunca. Causa: el matcher no coincide con el nombre exacto de la tool. Solución: usa "Edit|Write" con regex o "*" para todas; revisa la documentación oficial para los nombres exactos en tu versión de Claude Code.

Error: el agente se queda colgado tras un Edit. Causa: el comando del hook no termina o pide input interactivo. Solución: redirige stdin (< /dev/null), añade timeout y siempre cierra con || true si el fallo no debe romper el flujo.

Error: jq: command not found al disparar el hook. Causa: el hook corre con tu shell pero sin tu PATH completo. Solución: usa rutas absolutas (/usr/bin/jq) o exporta el PATH en un SessionStart.

Error: el guardrail bloquea pero el agente no entiende por qué. Causa: el mensaje va a stdout en lugar de stderr, o el exit code no es 2. Solución: imprime a stderr con echo "..." >&2 y devuelve exit 2 para que el agente reciba el feedback.

Preguntas Frecuentes

¿Los hooks de Claude Code reemplazan a los pre-commit hooks de git?

No, son complementarios. Los hooks de Claude Code corren durante la sesión del agente, antes de que el código llegue a un commit. Los pre-commit de git corren al hacer git commit. Lo ideal es tener ambos: el primero acelera el feedback durante la generación, el segundo es la red de seguridad final.

¿Puedo bloquear el uso de ciertas tools sin tocar permisos?

Sí. Un PreToolUse con matcher sobre la tool y exit 2 bloquea esa llamada concreta y devuelve un mensaje al agente. Es más expresivo que los permisos básicos porque puedes condicionar el bloqueo al contenido del comando, no solo al nombre de la tool.

¿Funcionan los hooks con subagentes?

Sí, los hooks aplican al harness completo. Cuando un subagente ejecuta una tool, los PreToolUse y PostToolUse también disparan. Es útil para mantener la misma política de formateo y bloqueos en flujos paralelos.

Cierre

Los hooks son la pieza menos vistosa del setup de Claude Code y, en mi experiencia, la que más reduce fricción a medio plazo. Mover el formateo, las validaciones y los bloqueos fuera del prompt te deja sesiones más cortas, más reproducibles y con menos drift entre lo que pides y lo que el agente entrega. Si ya tienes permisos y memoria configurados, esto es el siguiente paso natural antes de complicarte con orquestación o subagentes.

¿Tienes algún hook que te haya salvado el día? Cuéntamelo en Twitter en @sergiomarquezp_. En el próximo artículo voy a entrar en cómo combinar hooks con MCPs para dejar que el agente reaccione a eventos externos sin perder control.

Compartir X LinkedIn