← Todos los articulos

Los hooks de Claude Code explicados: la capa determinista alrededor de tu agente

De la guía: Claude Code Comprehensive Guide

¿Qué son los hooks de Claude Code? Los hooks son comandos de shell definidos por el usuario (además de endpoints HTTP, herramientas MCP y prompts al modelo) que Claude Code ejecuta automáticamente en puntos fijos de su ciclo de vida: antes de una llamada a una herramienta, después de una edición, al iniciar la sesión, cuando Claude termina de responder.1 Mientras que CLAUDE.md le da al modelo instrucciones que probablemente seguirá, los hooks se ejecutan coopere o no el modelo. Escribe /hooks dentro de cualquier sesión para ver cada evento del ciclo de vida y lo que hay conectado a él. {.answer-block}

La mayoría de los desarrolladores usan Claude Code con dos capas de control: los permisos, que filtran lo que el agente puede hacer, y CLAUDE.md, que describe lo que debería hacer. Los hooks son la tercera capa, y la única que garantiza algo. A continuación: el modelo mental, todos los eventos del ciclo de vida que recoge la documentación actual, el contrato exacto de entrada y salida, la configuración, cinco patrones funcionales y un marco de decisión. Cada detalle de la API se verificó el 8 de agosto de 2026 contra la referencia oficial de hooks y la guía; este sistema se mueve rápido, así que donde este artículo y la referencia discrepen, gana la referencia. (¿Es tu primera vez con Claude Code? Empieza por la instalación en 5 minutos o por la ruta de inicio.)

En resumen: los hooks reciben JSON por stdin y responden con códigos de salida o con JSON por stdout. El código 0 permite, el código 2 bloquea (en los eventos capaces de bloquear) y el código 1 —el código de fallo convencional en Unix— no bloquea nada, que es la trampa más grande de todo el sistema.2 Se configuran en settings.json bajo nombres de eventos como PreToolUse y Stop, filtrados por matchers. Usa hooks para todo lo que siempre debe ocurrir; usa CLAUDE.md para todo lo que el modelo simplemente debería saber.

El modelo mental: garantías alrededor de un núcleo no determinista

Un agente de programación es un sistema probabilístico. Pídele que ejecute Prettier después de cada edición y lo hará, casi siempre. Puede saltarse el paso cuando el cambio parece trivial, cuando el contexto se alarga o cuando tu forma de pedirlo cae distinto. CLAUDE.md, las skills y los prompts son todos sugerencias: de buena calidad, casi siempre atendidas, nunca garantizadas.

Los hooks son la envoltura determinista alrededor de ese núcleo. La guía abre con una definición de una sola línea – «Los hooks son comandos de shell definidos por el usuario.» – y expone el propósito sin rodeos: los hooks te dan «control determinista: ciertas acciones siempre ocurren en lugar de depender de que el LLM decida ejecutarlas».3 (Esa única línea de la guía subestima la superficie actual; la definición más amplia de la referencia ya suma endpoints HTTP y prompts al LLM, y los handlers también existen como herramientas MCP – lo verás más abajo, en Configuración.) El formateador se dispara en cada edición. La barrera de comandos evalúa cada llamada a Bash. La compuerta de cierre revisa cada final.

La aplicación es real, no cosmética: los hooks PreToolUse se disparan antes de cualquier comprobación del modo de permisos, así que un hook que devuelve permissionDecision: "deny" bloquea la herramienta incluso en modo bypassPermissions o bajo --dangerously-skip-permissions. Lo contrario no se cumple: un hook que devuelve "allow" no puede relajar las reglas de denegación de la configuración. Los hooks pueden endurecer la política más allá de lo que los permisos autorizan, nunca debilitarla.4

El ciclo de vida: todos los eventos de hook

Al 8 de agosto de 2026, la referencia documenta 31 eventos de hook.1 Se reparten en tres cadencias: una vez por sesión (SessionStart, SessionEnd), una vez por turno (UserPromptSubmit, Stop, StopFailure) y en cada llamada a una herramienta dentro del bucle agéntico (PreToolUse, PostToolUse). El resto se dispara ante condiciones concretas: cambios de configuración, compactación, subagentes, interacciones MCP.

Evento Se dispara Un uso real
SessionStart La sesión comienza o se reanuda Inyectar la rama de git y los issues abiertos como contexto
Setup --init-only, o --init/--maintenance en modo -p Instalar dependencias en CI antes de que arranque el agente
UserPromptSubmit Envías un prompt, antes de que Claude lo procese Añadir la fecha actual; rechazar prompts que contengan secretos
UserPromptExpansion Un comando escrito se expande en un prompt Auditar o vetar las expansiones de skills y comandos
PreToolUse Antes de que se ejecute una llamada a una herramienta Bloquear comandos de shell destructivos
PermissionRequest Aparece un diálogo de permisos Aprobar automáticamente los comandos de confianza para que no te pregunte
PermissionDenied El clasificador del modo automático deniega una llamada Devolver retry: true para que el modelo pueda reintentar
PostToolUse Después de que una llamada a una herramienta tenga éxito Formatear automáticamente cada archivo editado
PostToolUseFailure Después de que una llamada a una herramienta falle Registrar los comandos fallidos para su análisis
PostToolBatch Tras un lote de llamadas paralelas, antes de la siguiente llamada al modelo Guardar un punto de control o detener el bucle agéntico
Notification Claude Code envía una notificación Aviso de escritorio cuando Claude necesita tu respuesta
MessageDisplay Mientras se muestra el texto de un mensaje del asistente Censurar en pantalla (solo la vista; la transcripción no cambia)
SubagentStart Se lanza un subagente Inyectar contexto propio del tipo de agente
SubagentStop Un subagente termina Validar la salida del subagente antes de que se devuelva
TaskCreated Se crea una tarea mediante TaskCreate Hacer cumplir reglas de nombres o de alcance de las tareas
TaskCompleted Una tarea se marca como completada Verificar los criterios de aceptación antes de que el cierre se mantenga
Stop Claude termina de responder Compuerta de cierre: impedir el fin hasta que pasen las pruebas
StopFailure El turno acaba por un error de la API Alertar ante rate_limit o billing_error (solo registro; la salida se ignora)
TeammateIdle Un compañero de un equipo de agentes va a quedar inactivo Mantener a los compañeros trabajando con una cola
InstructionsLoaded Un archivo CLAUDE.md o .claude/rules/*.md entra en el contexto Registrar qué instrucciones entraron en la sesión
ConfigChange Un archivo de configuración cambia a mitad de sesión Bloquear ediciones no autorizadas de la configuración
CwdChanged Cambia el directorio de trabajo Recargar entornos al estilo de direnv
DirectoryAdded Se registra un directorio de trabajo a mitad de sesión con /add-dir o con register_repo_root del SDK (v2.1.219+) Cargar el contexto de ese repositorio en cuanto se une a la sesión
FileChanged Un archivo vigilado cambia en el disco Refrescar las variables de entorno cuando cambia .env
WorktreeCreate Se crea un worktree con --worktree o isolation: "worktree" Sustituir el aprovisionamiento por defecto de worktrees de git
WorktreeRemove Se elimina un worktree Limpieza a medida al terminar la sesión o el subagente
PreCompact Antes de la compactación del contexto Guardar el estado que no te puedes permitir perder
PostCompact Cuando termina la compactación Reinyectar el contexto crítico
Elicitation Un servidor MCP pide datos al usuario Rellenar formularios automáticamente en ejecuciones headless
ElicitationResult Después de que respondas a una petición MCP Validar o sustituir la respuesta antes de que se devuelva
SessionEnd La sesión termina Archivar registros, liberar recursos

No vas a necesitar la mayoría de ellos. Casi cualquier montaje de producción se construye con cinco: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart y Stop. El resto existe para el día en que los necesites.

El contrato: JSON de entrada, códigos de salida o JSON de salida

Los hooks de tipo comando reciben JSON por stdin y responden mediante códigos de salida, stdout y stderr. (Los hooks HTTP reciben el mismo JSON como cuerpo de un POST y responden mediante el cuerpo de la respuesta.)2

Cada evento entrega un sobre común —session_id, transcript_path, cwd y hook_event_name, con permission_mode en la mayoría de los eventos— más campos propios del evento. Un hook PreToolUse para un comando de Bash recibe:

{
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
  "cwd": "/home/user/my-project",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "npm test" }
}

Otros eventos cambian el final: UserPromptSubmit lleva prompt, SessionStart lleva source (startup/resume/clear/compact/fork – el quinto valor llegó con las sesiones bifurcadas en la v2.1.214, y un hook que filtre por source copiado de una lista antigua de cuatro valores se saltará los forks en silencio), Stop lleva stop_hook_active y last_assistant_message. Los hooks disparados dentro de subagentes reciben además agent_id y agent_type.2

Códigos de salida

Tres desenlaces:2

  • Código 0: éxito. Claude Code analiza stdout en busca de campos JSON de salida. En la mayoría de los eventos stdout va solo al registro de depuración; en UserPromptSubmit, UserPromptExpansion y SessionStart, un stdout plano se añade como contexto que Claude puede ver.
  • Código 2: error bloqueante. Se ignora stdout (incluido cualquier JSON); stderr se le devuelve a Claude como mensaje de error. Qué significa «bloquear» depende del evento.
  • Cualquier otro código de salida: error no bloqueante. La transcripción muestra un aviso <hook name> hook error y la ejecución continúa.

Esa última línea merece negrita: el código 1 no bloquea nada. La documentación avisa de esto directamente: Claude Code trata el código 1 como un error no bloqueante y sigue adelante, aunque 1 sea el código de fallo convencional en Unix. Los hooks de política deben hacer exit 2.2

Qué hace el código 2 en cada evento:2

Evento Efecto del código 2
PreToolUse Bloquea la llamada a la herramienta
PermissionRequest Deniega el permiso
UserPromptSubmit Bloquea el procesamiento y borra el prompt
UserPromptExpansion Bloquea la expansión
Stop / SubagentStop Impide detenerse; la conversación continúa
TeammateIdle Impide que el compañero quede inactivo
TaskCreated / TaskCompleted Revierte la creación / impide la finalización
ConfigChange Bloquea el cambio de configuración (salvo policy_settings)
PreCompact Bloquea la compactación
PostToolBatch Detiene el bucle agéntico antes de la siguiente llamada al modelo
Elicitation / ElicitationResult Deniega la petición / convierte la respuesta en un rechazo
WorktreeCreate Cualquier código distinto de cero aborta la creación del worktree

Todo lo demás no puede bloquear. PostToolUse y PostToolUseFailure le muestran stderr a Claude (la herramienta ya se ejecutó); SessionStart, Notification, SessionEnd, CwdChanged, FileChanged, PostCompact, SubagentStart y Setup muestran stderr solo al usuario, mientras que DirectoryAdded manda stderr únicamente al registro de depuración; StopFailure, InstructionsLoaded, MessageDisplay y PermissionDenied ignoran el código de salida y, en el caso de PermissionDenied, la única palanca es el campo JSON retry: true.2

Salida JSON

Para un control más fino que bloquear o callar, sal con el código 0 e imprime un objeto JSON en stdout. Una regla por delante: códigos de salida o JSON, nunca ambos; el JSON solo se procesa con el código 0, y el código 2 lo descarta.5

Hay campos universales que funcionan en todos los eventos: continue: false detiene a Claude por completo (con stopReason visible para el usuario), suppressOutput oculta stdout de la transcripción, systemMessage le muestra un aviso al usuario y terminalSequence emite una secuencia de escape de terminal permitida (notificación de escritorio, título de ventana, campana). Los campos de decisión, en cambio, dependen del evento:5

Eventos Patrón de decisión Campos clave
UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact decision de primer nivel decision: "block" + reason (se le muestra a Claude). Omite decision para permitir
PreToolUse hookSpecificOutput permissionDecision: "allow" | "deny" | "ask" | "defer", más permissionDecisionReason y updatedInput para reescribir los argumentos de la herramienta antes de ejecutarla
PermissionRequest hookSpecificOutput decision.behavior: "allow" | "deny", con decision.updatedInput opcional
PermissionDenied hookSpecificOutput retry: true le indica al modelo que puede reintentar
PostToolUse hookSpecificOutput updatedToolOutput sustituye el resultado de la herramienta
Stop / SubagentStop hookSpecificOutput additionalContext: comentarios sin carácter de error que continúan la conversación sin contar como error de hook
SessionStart, Setup, SubagentStart Solo contexto additionalContext, más initialUserMessage, sessionTitle, watchPaths y reloadSkills, exclusivos de SessionStart. Sin bloqueo
MessageDisplay hookSpecificOutput displayContent sustituye únicamente el texto en pantalla
Elicitation / ElicitationResult hookSpecificOutput action: "accept" | "decline" | "cancel", más content
TeammateIdle, TaskCreated, TaskCompleted continue universal continue: false + stopReason detiene por completo el flujo del compañero o de la tarea (el código 2 es el bloqueo propio del evento)
WorktreeCreate Devolución de una ruta Los hooks de tipo comando imprimen la ruta del worktree en stdout; los hooks HTTP devuelven hookSpecificOutput.worktreePath; un fallo o una ruta ausente hacen fracasar la creación
WorktreeRemove, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, DirectoryAdded, FileChanged Ninguno Solo efectos secundarios

Dos detalles que hacen daño. Primero, PreToolUse es la excepción al patrón del decision de primer nivel: históricamente usó ahí decision/reason, pero ambos están obsoletos para este evento ("approve"/"block" se corresponden con "allow"/"deny"); usa hookSpecificOutput.permissionDecision.5 Segundo, cuando varios hooks PreToolUse discrepan, la precedencia es deny > defer > ask > allow – gana la respuesta más restrictiva. Aun así, dale a cada decisión un único hook responsable en vez de apoyarte en ese desempate.5

Configuración: settings.json, matchers, alcance

La configuración de hooks se anida en tres niveles: elige un evento, añade un grupo de matchers para filtrar cuándo se dispara y define uno o más handlers de hook que se ejecuten.6

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "/path/to/lint-check.sh" }
        ]
      }
    ]
  }
}

Dónde pongas esto determina el alcance: ~/.claude/settings.json se aplica a todos tus proyectos, .claude/settings.json es propio del proyecto y se puede versionar, .claude/settings.local.json es propio del proyecto y queda fuera de git, y rige la precedencia habitual de la configuración: política gestionada sobre local, sobre proyecto, sobre usuario.9 Los hooks también pueden venir en plugins (hooks/hooks.json) y en el frontmatter de una skill o de un agente, y los administradores de empresa pueden imponer hooks gestionados que los usuarios no pueden anular.6

Los matchers se evalúan según sus caracteres: "*", "" o un matcher omitido casan con todo; un valor que solo contenga letras, dígitos, _, -, espacios, comas y | es una cadena exacta o una lista (Bash, Edit|Write); cualquier otra cosa se convierte en una expresión regular de JavaScript sin anclar, de modo que Edit.* casa tanto con Edit como con NotebookEdit; ancla con ^Edit$ cuando te refieras a una sola herramienta. Los matchers distinguen mayúsculas de minúsculas, y cada evento casa contra su propio campo: el nombre de la herramienta en los eventos de herramientas, source en SessionStart, el tipo de agente en SubagentStart y el tipo de notificación en Notification.6 Para afinar el filtrado en los eventos de herramientas, el campo if de cada handler acepta una regla de permisos como "Bash(git *)", aunque funciona en la medida de lo posible (se abre si el comando no se puede analizar), así que para garantías firmes usa reglas de permisos y no if.6 Un cambio de semántica que conviene conocer: desde la v2.1.214, un patrón de ruta de un solo segmento en if (como Edit(src/**)) solo casa con un src de primer nivel bajo el directorio de trabajo; un if escrito antes de esa versión deja de casar en silencio con rutas anidadas como packages/app/src/ – escribe Edit(**/src/**) para recuperar el comportamiento a cualquier profundidad.6

Los handlers vienen en cinco tipos: command (shell), http (endpoint POST), mcp_tool, prompt (evaluación del modelo en un turno) y agent (un subagente con acceso a Read/Grep/Glob; experimental). Tiempos de espera por defecto: 600 segundos para command/http/mcp_tool (bajados a 30 en UserPromptSubmit y a 10 en MessageDisplay), 30 para prompt y 60 para agent; se ajustan hook por hook con timeout.6 Todos los hooks coincidentes se ejecutan en paralelo, con los handlers idénticos deduplicados, y $CLAUDE_PROJECT_DIR apunta tus scripts a la raíz del proyecto.

Compruébalo con /hooks: un navegador de solo lectura que muestra cada evento, sus hooks configurados y de qué archivo de configuración salió cada uno. Para cambiar algo, edita el JSON (o pídeselo a Claude). Para silenciarlo todo temporalmente, pon "disableAllHooks": true.6

Cinco patrones

Genéricos y mínimos. El tutorial de hooks construye versiones de producción más completas de varios de ellos, y Hooks para el desarrollo en Apple los aplica a la cadena de herramientas de iOS.

1. Formateo automático tras las ediciones (PostToolUse)

Directo de la guía oficial: todo archivo que Claude toca queda formateado, sin excepciones:3

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

Cambia el comando por ruff format, gofmt o swiftformat, según pida tu stack.

2. Bloquear comandos peligrosos (PreToolUse, código 2)

#!/bin/bash
# .claude/hooks/guard-bash.sh — register on PreToolUse, matcher "Bash"
command=$(jq -r '.tool_input.command // empty')
case "$command" in
  *"rm -rf"* | *"git push --force"* | *"DROP TABLE"*)
    echo "Blocked: matches a destructive pattern. Propose a safer alternative." >&2
    exit 2 ;;
esac
exit 0

El código 2 bloquea la llamada y le devuelve stderr a Claude, que corrige el rumbo en lugar de reintentar a ciegas. El equivalente en JSON —permissionDecision: "deny" con un motivo— hace lo mismo y deja margen para crecer hacia "ask" (escalar a la persona) o updatedInput (reescribir el comando).5

3. Inyectar contexto al iniciar la sesión (SessionStart)

Un stdout plano desde un hook SessionStart se convierte en contexto que Claude puede ver, sin necesidad de JSON:1

#!/bin/bash
# .claude/hooks/session-context.sh — register on SessionStart
echo "Current branch: $(git branch --show-current)"
echo "Recent commits:"
git log --oneline -5
echo "Uncommitted files: $(git status --porcelain | wc -l | tr -d ' ')"
exit 0

Reserva esto para el estado dinámico. Las convenciones estáticas van en CLAUDE.md, que es lo que recomienda la propia documentación para el contexto que no requiere un script.1

4. Una compuerta de cierre sobre Stop

Stop se dispara cuando Claude termina de responder. Bloquearlo obliga al agente a seguir trabajando hasta que se cumpla una condición:

#!/bin/bash
# .claude/hooks/stop-gate.sh — register on Stop
input=$(cat)
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0  # already continuing because of this hook; don't loop forever
fi
if ! npm test --silent > /tmp/stop-gate.log 2>&1; then
  jq -n '{decision: "block", reason: "Tests are failing. Fix them before finishing. Log: /tmp/stop-gate.log"}'
fi
exit 0

La comprobación de stop_hook_active importa: Claude Code limita por defecto un hook de Stop a 8 bloqueos consecutivos (ampliable con CLAUDE_CODE_STOP_HOOK_BLOCK_CAP), y una compuerta que nunca comprueba si ella misma provocó la continuación los quemará todos de golpe.7 Para una guía más suave, devuelve hookSpecificOutput.additionalContext en vez de decision: "block": la misma continuación, pero como comentario etiquetado y no como error de hook. Y para condiciones puntuales, el comando integrado /goal es un hook de Stop basado en prompt, limitado a la sesión y sin nada que configurar.1

5. El dispatcher: un punto de entrada, muchos hooks pequeños

Registrar diez hooks significa diez entradas en settings.json que se desincronizan entre máquinas y proyectos. La alternativa: registrar un dispatcher por evento y enrutar por convención.

#!/bin/bash
# .claude/hooks/dispatch.sh — register once per event you care about
input=$(cat)
event=$(echo "$input" | jq -r '.hook_event_name')
dir="$CLAUDE_PROJECT_DIR/.claude/hooks/$event"
[ -d "$dir" ] || exit 0
for hook in "$dir"/*.sh; do
  [ -x "$hook" ] || continue
  echo "$input" | "$hook" || exit $?
done
exit 0

Añadir una barrera ahora es un chmod +x sobre un archivo nuevo en .claude/hooks/PreToolUse/: settings.json no cambia nunca, cada script se mantiene lo bastante pequeño para probarlo por separado y el primer código 2 se propaga. Una advertencia: el dispatcher serializa lo que Claude Code ejecutaría en paralelo, y encaja mejor con hooks basados en códigos de salida; un hook que emita JSON debería seguir siendo independiente, ya que stdout tiene que contener exactamente un objeto JSON.5

Hook, CLAUDE.md, skill o memoria

Cuatro mecanismos, cuatro trabajos:

Mecanismo Trabajo Regla para elegir
Hook Aplicación Si saltárselo tiene que ser imposible —formateo, seguridad, compuertas—, es un hook
CLAUDE.md Orientación Si es una convención que el modelo debería conocer en cada sesión —stack, estilo, comandos—, es CLAUDE.md
Skill Capacidad Si es un procedimiento con sus propias instrucciones y scripts, invocado cuando toca, es una skill
Memoria Recuerdo Si es un dato aprendido en una sesión que las sesiones futuras necesitan, es memoria

El modo de fallo actúa en ambas direcciones. Codificar convenciones como hooks te deja scripts frágiles que imponen cosas que una frase de orientación resolvería sin problema. Codificar la política como prosa en CLAUDE.md te deja un agente que hace force-push a main justo el día en que importa. La prueba: ¿cuánto cuesta que el modelo ignore esto una sola vez? Molestia → CLAUDE.md. Incidente → hook.

Lo que los hooks no pueden hacer

Límites honestos, todos de la documentación oficial:7

  • Los hooks no pueden invocar herramientas ni comandos de barra. Los hooks de tipo comando hablan stdout, stderr y códigos de salida; nada más. El contexto devuelto con additionalContext se inyecta como texto plano.
  • PostToolUse no puede deshacer. La herramienta ya se ejecutó. La prevención vive en PreToolUse.
  • Stop se dispara al final de cada respuesta, no solo cuando la «tarea está completa», y nunca ante una interrupción del usuario (los errores de la API disparan StopFailure en su lugar). La lógica de la compuerta tiene que tolerar paradas a mitad de tarea.
  • PermissionRequest no se dispara en ejecuciones headless (-p) simples. Sí se dispara con -p cuando un callback canUseTool del Agent SDK aporta la solicitud, y también en las llamadas de subagentes en segundo plano; para todo lo demás automatizado, usa PreToolUse.
  • PreToolUse no ve los archivos referenciados con @. Los archivos que traes con @ en tu prompt no implican ninguna llamada a herramienta; protege esas rutas con reglas de denegación sobre Read.1
  • El updatedInput en paralelo es poco fiable por diseño. Cuando varios hooks PreToolUse reescriben los argumentos de la misma herramienta, solo sobrevive una reescritura y tú no eliges cuál. Deja que un único hook sea dueño de cada reescritura.
  • Los tiempos de espera cancelan el hook. 600 segundos por defecto en los hooks de tipo comando (30 en UserPromptSubmit, 10 en MessageDisplay); una compuerta lenta que expira es una compuerta que no se ejecutó.
  • La salida está limitada a 10.000 caracteres: el excedente se escribe en un archivo y se sustituye por una vista previa.
  • Los hooks se ejecutan con todos tus permisos de usuario. La propia advertencia de la referencia: «pueden modificar, eliminar o acceder a cualquier archivo al que pueda acceder tu cuenta de usuario. Revisa y prueba todos los comandos de hook antes de añadirlos a tu configuración».8 Entrecomilla tus variables, usa rutas absolutas, deja fuera los archivos sensibles.
  • Un hook roto degrada todas las sesiones hasta que se arregle. Depúralo con la vista de transcripción (Ctrl+O), con claude --debug-file /tmp/claude.log o con /debug a mitad de sesión; un clásico es un perfil de shell que imprime algo al arrancar y corrompe la salida JSON de tu hook.7

Preguntas frecuentes

¿Qué son los hooks de Claude Code?

Los hooks son comandos definidos por el usuario —scripts de shell, endpoints HTTP, herramientas MCP o prompts al modelo— que Claude Code ejecuta automáticamente en puntos concretos del ciclo de vida.3 Reciben el JSON del evento por stdin y responden con códigos de salida o con JSON: bloquear una llamada a una herramienta, inyectar contexto, reescribir argumentos, mantener al agente trabajando. A diferencia de las instrucciones de CLAUDE.md, se ejecutan siempre, sea cual sea el comportamiento del modelo.

¿En qué se diferencian los hooks PreToolUse de los permisos?

Las reglas de permisos son declarativas: patrones estáticos de permitir, denegar o preguntar que Claude Code evalúa por sí mismo. Los hooks PreToolUse, en cambio, son programables: tu código inspecciona la entrada completa de la herramienta y decide. Los hooks se disparan antes de las comprobaciones del modo de permisos, así que un "deny" de un hook se sostiene incluso en modo bypassPermissions, pero un "allow" de un hook no puede saltarse una regla de denegación de la configuración.4 Usa reglas de permisos para todo lo que un patrón sepa expresar; recurre a un hook cuando la decisión necesite lógica, estado externo o una reescritura de la entrada.

¿Funcionan los hooks en modo headless (-p)?

Sí, con un matiz: los hooks PermissionRequest se saltan las ejecuciones -p simples (allí nada aporta una solicitud de permiso), aunque sí se disparan cuando un callback canUseTool del Agent SDK aporta una, y también en las llamadas de subagentes en segundo plano. Las decisiones de permisos automatizadas para las ejecuciones headless simples corresponden a PreToolUse.7 El modo headless desbloquea además una opción que las sesiones interactivas ignoran: permissionDecision: "defer", que pausa una llamada a una herramienta para que un proceso envolvente (una aplicación con el Agent SDK, una interfaz propia) recoja los datos y reanude la sesión más tarde.5

¿Por qué mi hook se ejecuta pero no bloquea nada?

Casi siempre es una violación del contrato. El código 1 no bloquea; solo el 2 lo hace, y únicamente en los eventos que admiten bloqueo.2 Las decisiones en JSON solo se analizan con el código 0: un script que imprime {"decision": "block"} y luego sale con 2 pierde su JSON. Y los matchers distinguen mayúsculas de minúsculas: bash nunca casa con Bash. Confirma el registro con /hooks y luego prueba pasando un JSON de ejemplo al script por una tubería y revisando echo $?.7

Fuentes

Verificado contra la documentación oficial el 8 de agosto de 2026. La API de hooks ha cambiado de forma sustancial a lo largo de las versiones v2.1.x de Claude Code (nuevos eventos, nuevos campos, nueva semántica de matchers), así que trata los detalles sensibles a la versión como válidos a esa fecha.

Relacionado en este sitio: la sección de hooks de la guía de Claude Code para la visión de conjunto, incluidos los hooks de tipo prompt y agente; el tutorial de hooks para cinco montajes de producción con sus configuraciones completas; Hooks para el desarrollo en Apple para los patrones aplicados a iOS; y la guía rápida si todavía no has instalado Claude Code.


  1. Anthropic, «Hooks reference — Hook lifecycle and hook events». code.claude.com/docs/en/hooks#hook-events 

  2. Anthropic, «Hooks reference — Hook input and output; exit code output; exit code 2 behavior per event». code.claude.com/docs/en/hooks#exit-code-output 

  3. Anthropic, «Automate actions with hooks». code.claude.com/docs/en/hooks-guide 

  4. Anthropic, «Hooks guide — Hooks and permission modes». code.claude.com/docs/en/hooks-guide#hooks-and-permission-modes 

  5. Anthropic, «Hooks reference — JSON output and decision control». code.claude.com/docs/en/hooks#json-output 

  6. Anthropic, «Hooks reference — Configuration: hook locations, matcher patterns, hook handler fields, el menú /hooks». code.claude.com/docs/en/hooks#configuration 

  7. Anthropic, «Hooks guide — Limitations and troubleshooting». code.claude.com/docs/en/hooks-guide#limitations-and-troubleshooting 

  8. Anthropic, «Hooks reference — Security considerations». code.claude.com/docs/en/hooks#security-considerations 

  9. Anthropic, «Claude Code settings». code.claude.com/docs/en/settings 

Artículos relacionados

Codex CLI vs Claude Code 2026: arquitectura, precios y acceso desde China

Codex CLI vs Claude Code en 2026: sandbox del kernel, gobernanza con hooks, contexto del modelo, precios, acceso a la nu…

39 min de lectura

Hooks de Claude Code: por qué existe cada uno de mis 95 hooks

Construí 95 hooks para Claude Code. Cada uno existe porque algo salió mal. Aquí están las historias de origen y la arqui…

11 min de lectura