Guía9 min

Hooks en Claude Code: automatiza y bloquea acciones del agente en el punto exacto

Resumen

Guía práctica de hooks en Claude Code para agentes de IA: configuración por evento y matcher en settings.json, JSON que llega por stdin, códigos de salida que bloquean de verdad (exit 2), decisiones allow, deny y ask con hookSpecificOutput, y un ejemplo real para frenar comandos destructivos sin tocar el turno del modelo.

Claude
Diagrama de un escudo que inspecciona las llamadas de herramientas de un agente de código

Qué resuelve

Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.

Un agente de código que solo "pide permiso" deja decisiones importantes al azar del modelo. Los hooks de Claude Code resuelven eso al revés: tú defines comandos que se ejecutan automáticamente en puntos fijos del ciclo de vida (antes de una herramienta, al enviar un prompt, al terminar una sesión) y el agente debe pasar por ahí sí o sí. Es la diferencia entre confiar en que el modelo decida bien y tener un control determinístico en el punto exacto donde se ejecuta la acción.

Esta guía cubre la configuración real en 2026: dónde viven los hooks, qué eventos existen, cómo escriben matchers sin sorpresas, qué significa cada código de salida y cómo bloquear acciones peligrosas con un ejemplo completo.

Qué problema resuelven los hooks

Un prompt o una regla en CLAUDE.md es una sugerencia: el modelo puede ignorarla. Un hook es ejecución: corre como proceso real y su decisión se aplica antes de que la herramienta toque tu disco. Usos típicos:

  • Bloquear comandos destructivos (rm -rf, git push --force, DROP TABLE) en PreToolUse, sin importar qué planea el modelo.
  • Inyectar contexto automático al iniciar sesión (SessionStart): rama activa, issues, estado del repo.
  • Avisar al modelo después de ejecutar algo (PostToolUse): lint que falló, test roto, advertencia de seguridad.
  • Auditar: registrar cada llamada a herramientas en un log externo sin tocar el flujo.

La regla mental: el prompt educa, el hook obliga. Para todo lo que sea política dura (seguridad, dinero, datos), hook. Para convenciones de estilo, contexto.

Dónde se configuran

Los hooks viven en archivos de settings JSON, con distinto alcance:

UbicaciónAlcanceSe comparte
~/.claude/settings.jsonTodos tus proyectosNo (local)
.claude/settings.jsonUn proyectoSí (commiteable)
.claude/settings.local.jsonUn proyectoNo (gitignored)
Managed policy settingsToda la organizaciónSí (admin)
Plugin hooks/hooks.jsonMientras el plugin esté activoSí (viene con el plugin)

La elección práctica: hooks de seguridad de repo van en .claude/settings.json (el equipo hereda la política al clonar); preferencias personales en ~/.claude/settings.json; experimentos en settings.local.json. En sesiones cloud de Claude Code on the web no se lee tu settings local: los hooks llegan del repo o de la organización, otro motivo para commitearlos.

La estructura tiene tres niveles: eliges el evento, opcionalmente un matcher que filtra cuándo aplica, y uno o más handlers (comandos shell) que se ejecutan cuando hay match.

Los eventos que importan

Claude Code dispara eventos en tres ritmos: una vez por sesión, una vez por turno y en cada llamada a herramientas del loop agéntico.

EventoCuándo disparaUso típico
SessionStartAl iniciar o reanudar sesiónInyectar contexto del repo
UserPromptSubmitAl enviar un prompt, antes de procesarloValidar o bloquear prompts
PreToolUseAntes de cada llamada a herramientaBloquear o permitir tools
PostToolUseDespués de cada llamada a herramientaFeedback al modelo, auditoría
StopCuando Claude intenta terminar el turnoForzar verificación antes de parar
SessionEndAl cerrar la sesiónLimpieza, reportes
PreCompact / PostCompactAntes/después de compactar contextoPreservar estado importante

Ciclo de vida de los hooks de Claude Code: eventos de sesión, por turno y por llamada a herramientas

Los dos que vas a usar casi siempre son PreToolUse (el control antes del daño) y PostToolUse (el feedback después de la acción). Stop es el subestimado: un hook que rechaza la parada obliga al agente a seguir trabajando, útil para gates de calidad tipo "no termines si el build falla".

Matchers: filtra por herramienta sin regex innecesaria

El matcher decide para qué tools se dispara el hook en PreToolUse y PostToolUse. La tabla del matcher es más simple de lo que parece:

MatcherComportamiento
"*" o vacíoMatch con todo
BashSolo la tool Bash (exacto)
Edit|WriteBash-style OR: Edit o Write
^NotebookRegex: cualquier tool que empiece con Notebook

Solo si el matcher contiene caracteres especiales se interpreta como regex JavaScript no anclada. Ojo con Edit.*: matchea tanto Edit como NotebookEdit; si quieres solo Edit, ancla con ^Edit$. Y en versiones viejas (< v2.1.195) un nombre con guion como code-reviewer caía en el camino regex y matcheaba de más: ancla con ^code-reviewer$ si tu organización aún corre builds viejos.

El JSON que llega por stdin

Tu hook recibe un JSON por stdin con contexto del evento. Para PreToolUse de un comando Bash:

{
  "session_id": "abc123",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test",
    "description": "Run test suite"
  },
  "cwd": "/home/user/my-project",
  "permission_mode": "default"
}

Campos que usarás en el 90% de los hooks: tool_name (qué herramienta), tool_input (con qué argumentos, incluido el comando crudo en Bash) y permission_mode (el modo actual: default, plan, acceptEdits, bypassPermissions). Con eso puedes tomar decisiones reales: bloquear rm salvo en un whitelist, exigir doble confirmación en modo bypassPermissions, etc.

Códigos de salida: lo que sí bloquea

Aquí está el detalle que más bugs evita. El exit code de tu hook decide:

Exit codeEfecto
0Éxito. stdout se procesa como JSON o texto según formato; no bloquea
2Error bloqueante: la acción se cancela y Claude ve tu stderr
Otro (1, 3...)No bloquea: es un error no bloqueante y el flujo sigue

El caso trampa: un hook que hace exit 1 con un mensaje de error no detiene la herramienta. Claude Code lo trata como fallo no bloqueante y la acción continúa. Si tu hook es una política de seguridad, el bloqueo es exit 2, sin excepciones. Y aunque imprimas JSON con permissionDecision: "allow", un exit 2 gana: el bloqueo por código es lo único que el JSON no puede anular.

Timeouts: un hook command que se pasa del timeout se cancela y no bloquea la tool call. No confíes en un hook colgado como si fuera un gate: si necesitas bloqueo garantizado, mantén el hook rápido y determinístico.

Decisiones estructuradas con hookSpecificOutput

Además del exit code, tu hook puede imprimir JSON en stdout con decisión estructurada. Para PreToolUse, el formato actual usa hookSpecificOutput (los campos top-level decision/reason están deprecados para este evento):

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Comando destructivo bloqueado por política del repo"
  }
}

Los tres valores de permissionDecision:

  • allow: salta el prompt de permiso y ejecuta la tool.
  • deny: bloquea la tool; la razón se muestra a Claude (puede proponer una alternativa).
  • ask: fuerza confirmación del usuario aunque el modo auto aprobara.

El ejemplo completo para bloquear rm destructivo, guardado en .claude/hooks/block-rm.sh con chmod +x:

#!/bin/bash
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command')

if [[ "$command" == rm* ]]; then
  echo "Blocked: rm commands are not allowed by repo policy" >&2
  exit 2
fi

exit 0

Y su registro en .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}

Panel de decisiones: allow, deny y ask aplicados en el punto exacto de la tool call

Checklist para escribir hooks que no rompan la sesión

  • Determinístico y rápido: un hook corre en cada tool call; nada de redes lentas ni scripts pesados. Si necesita estado, escribe a un log local.
  • Fail-open claro: si tu hook falla por un bug, ¿debería bloquear todo? Para políticas de seguridad el fail-closed es exit 2; para conveniencia, mejor no bloquear y avisar.
  • Usa $CLAUDE_PROJECT_DIR para rutas: el hook corre desde el cwd de la sesión, y una ruta relativa rompe en sesiones desde subcarpetas.
  • jq sobre parsing manual: el JSON llega por stdin y jq -r es la forma estable de extraer campos sin regex frágiles.
  • Prueba primero con --debug: los hooks fallidos silenciosos (ruta mal escrita en settings.json) dejan la política sin efecto. El debug log muestra qué hooks matchearon y sus exit codes.
  • PostToolUse para feedback, no veto: el tool call ya corrió; sirve para avisar al modelo de errores que debe corregir, no para "deshacer".

Errores comunes

"Mi hook no hace nada" — revisa la ruta del comando en settings.json y ejecuta claude --debug para ver el log. El fallo más común es una ruta que no existe o un script sin chmod +x: el hook "corre", falla con 127 y el flujo sigue como si nada.

"Puse exit 1 y no bloqueó" — es el comportamiento documentado: solo exit 2 bloquea por código. Cambia el 1 por 2 o usa JSON con permissionDecision: "deny".

"El hook matchea de más" — probable regex sin anclar: Edit.* incluye NotebookEdit. Usa ^Edit$ o la lista exacta Edit|Write.

"El JSON del hook no se aplica" — en eventos con modelo de decisión estándar, un JSON inválido produce error no bloqueante y la acción sigue. Valida el JSON (por ejemplo, con jq . en desarrollo) antes de depender de él.

FAQ

¿Los hooks funcionan en subagentes?

Sí. Los hooks de settings, managed policy y plugins también corren dentro de subagentes: cuando un subagente llama a una tool, los eventos PreToolUse y PostToolUse disparan los mismos hooks, y el JSON de entrada trae agent_id y agent_type para distinguir la llamada del hilo principal.

¿Se pueden ejecutar hooks HTTP o con MCP tools?

Sí. Además del tipo command, los handlers pueden ser endpoints HTTP (el JSON llega como body del POST) o llamadas a MCP tools. Para endpoints HTTP, las allowlists allowedHttpHookUrls e httpHookAllowedEnvVars de settings controlan qué URLs se permiten y qué variables de entorno se interpolan en headers.

¿Los hooks reemplazan el sistema de permisos?

No, lo complementan. Las reglas de allow/deny de permisos se evalúan igual, decida lo que decida el hook: un allow del hook no anula una deny de permisos, y un deny del hook se suma a las reglas existentes. Piensa en los hooks como una capa de política programable encima del modelo de permisos.

¿Puedo inyectar contexto con hooks?

Sí. SessionStart acepta additionalContext en su hookSpecificOutput (o texto plano en stdout) y PreToolUse también acepta additionalContext junto a la decisión, útil para pasarle al modelo información del entorno justo antes de ejecutar una tool.

Conclusión

Los hooks convierten a Claude Code de "agente que pide permiso" a "agente con puntos de control verificables": el evento correcto, un matcher exacto y un exit code que sí bloquea. Empieza por uno solo: un PreToolUse en Bash que frene los comandos destructivos de tu repo. Cuando ese hook lleve unas semanas salvándote de un rm mal generado, el resto (contexto de sesión, gates de calidad en Stop, auditoría en PostToolUse) se construye solo.

Si el siguiente paso es aislar el entorno completo donde corre el agente, la guía de sandboxing y permisos para agentes de código cubre la otra mitad: qué puede tocar el agente una vez que la acción ya pasó el hook. Para proteger los datos que el agente lee y escribe, revisa cómo evitar fugas de datos en agentes de IA. Y cuando empieces a delegar trabajo a subagentes, cuándo delegar en subagentes (y cuándo no) marca la línea entre paralelizar bien y romper el repo.