Los hooks de Claude Code explicados: la capa determinista alrededor de tu agente
¿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,UserPromptExpansionySessionStart, 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 errory 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
additionalContextse inyecta como texto plano. PostToolUseno puede deshacer. La herramienta ya se ejecutó. La prevención vive enPreToolUse.Stopse 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 disparanStopFailureen su lugar). La lógica de la compuerta tiene que tolerar paradas a mitad de tarea.PermissionRequestno se dispara en ejecuciones headless (-p) simples. Sí se dispara con-pcuando un callbackcanUseTooldel Agent SDK aporta la solicitud, y también en las llamadas de subagentes en segundo plano; para todo lo demás automatizado, usaPreToolUse.PreToolUseno 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 sobreRead.1- El
updatedInputen 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 enMessageDisplay); 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), conclaude --debug-file /tmp/claude.logo con/debuga 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.
-
Anthropic, «Hooks reference — Hook lifecycle and hook events». code.claude.com/docs/en/hooks#hook-events ↩↩↩↩↩↩
-
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 ↩↩↩↩↩↩↩↩
-
Anthropic, «Automate actions with hooks». code.claude.com/docs/en/hooks-guide ↩↩↩
-
Anthropic, «Hooks guide — Hooks and permission modes». code.claude.com/docs/en/hooks-guide#hooks-and-permission-modes ↩↩
-
Anthropic, «Hooks reference — JSON output and decision control». code.claude.com/docs/en/hooks#json-output ↩↩↩↩↩↩↩
-
Anthropic, «Hooks reference — Configuration: hook locations, matcher patterns, hook handler fields, el menú /hooks». code.claude.com/docs/en/hooks#configuration ↩↩↩↩↩↩↩
-
Anthropic, «Hooks guide — Limitations and troubleshooting». code.claude.com/docs/en/hooks-guide#limitations-and-troubleshooting ↩↩↩↩↩
-
Anthropic, «Hooks reference — Security considerations». code.claude.com/docs/en/hooks#security-considerations ↩
-
Anthropic, «Claude Code settings». code.claude.com/docs/en/settings ↩