30 eventos de hook se disparan en una sesión de Claude Code. Solo 3 pueden responder.

La lista completa de eventos de hook de Claude Code, cuándo se dispara cada uno, cuáles son los 15 que pueden bloquear y la regla de stdout que se traga en silencio la salida de la mayoría de los hooks. Una referencia de campo construida ejecutando hooks en producción a lo largo de miles de sesiones de agentes.

Dos fallos aparecen una y otra vez cuando se conectan hooks a Claude Code, y no se parecen en nada.

El primero: añades un hook y no pasa nada. Ni error, ni aviso, ni línea de log. El hook simplemente no se ejecuta nunca.

El segundo: el hook se ejecuta claramente, ves sus efectos en el disco, pero el mensaje que imprime para el agente no llega nunca. El agente se comporta como si el hook no hubiera dicho nada.

Los dos vienen del mismo sitio. El sistema de hooks es más grande y menos uniforme que el puñado de eventos que cubren la mayoría de los artículos, y las reglas sobre quién puede hablarle al agente no son las que uno supondría. Esta es la referencia que nos habría gustado tener. Construimos AgentsRoom sobre estos hooks, y todo lo que sigue está citado de la referencia oficial o medido en producción.

Hay 30 eventos, no seis

La mayoría de las guías cubren PreToolUse, PostToolUse, UserPromptSubmit, Stop, Notification y SubagentStop. Esos seis son reales, y cargan con casi todo el trabajo útil. También son la quinta parte de lo que existe.

La lista completa, agrupada por lo que observa cada uno:

GrupoEventos
SesiónSessionStart, SessionEnd, Setup
PromptUserPromptSubmit, UserPromptExpansion
HerramientasPreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch
PermisosPermissionRequest, PermissionDenied
TurnoStop, StopFailure
Subagentes y tareasSubagentStart, SubagentStop, TaskCreated, TaskCompleted, TeammateIdle
ContextoPreCompact, PostCompact, InstructionsLoaded
EntornoFileChanged, CwdChanged, ConfigChange
WorktreesWorktreeCreate, WorktreeRemove
InterfazNotification, MessageDisplay
Elicitation MCPElicitation, ElicitationResult

Diagrama cronológico de los 30 eventos de hook de Claude Code en el orden en que se disparan durante una sesión de agente, desde SessionStart hasta SessionEnd pasando por UserPromptSubmit, PreToolUse, PostToolUse y Stop, con los eventos que pueden bloquear al agente.

El orden en que se disparan los eventos dentro de una misma sesión. El bloque de herramienta se repite una vez por llamada a herramienta, y el bloque de prompt entero se repite una vez por turno.

Algunos de estos eventos cambian la forma de pensar el sistema. PostToolUseFailure existe, así que la rama «¿funcionó la herramienta?» es un evento, no algo que se deduzca de un payload. PostToolBatch se dispara una sola vez cuando se resuelve un lote de llamadas a herramientas en paralelo, que es el sitio adecuado para lanzar un linter una vez en lugar de una vez por edición. InstructionsLoaded se dispara cuando se lee CLAUDE.md, lo que te da un punto de enganche para comprobar que el agente cargó realmente las reglas que crees que cargó.

La regla de stdout que se traga la salida de la mayoría de los hooks

Es lo más útil de esta página.

Con código de salida 0, Claude Code analiza stdout en busca de campos JSON de salida. Pero que ese stdout llegue a mostrarse al agente depende del evento, y las excepciones caben en una lista corta. De la referencia:

Para la mayoría de los eventos, stdout se escribe en el log de depuración pero no se muestra en la transcripción. Las excepciones son UserPromptSubmit, UserPromptExpansion y SessionStart, donde stdout se añade como contexto que Claude puede ver y sobre el que puede actuar.

Tres eventos de treinta. Si haces echo "warning: this migration is destructive" desde un hook PostToolUse esperando que el agente lo lea, no lo leerá nunca. Tu texto se fue al log de depuración.

Hay exactamente dos formas de poner texto delante del agente desde cualquier otro evento:

  1. Salir con 2 y escribir en stderr. Con salida 2, Claude Code ignora stdout y cualquier JSON que contenga, y devuelve stderr al agente como mensaje de error.
  2. Salir con 0 e imprimir un objeto JSON que lleve hookSpecificOutput.additionalContext.

Fíjate en la asimetría del primero. Salida 0 significa que stdout cuenta y stderr no. Salida 2 significa que stderr cuenta y stdout se descarta por completo. Invertir esto es la razón por la que un hook puede parecer perfectamente correcto y seguir mudo.

Cualquier otro código de salida es un error no bloqueante. La transcripción muestra un aviso <hook name> hook error con la primera línea de stderr, la ejecución continúa y el stderr completo acaba en el log de depuración.

Diagrama de los códigos de salida de los hooks de Claude Code: la salida 0 envía el JSON de stdout al agente solo en tres eventos, la salida 2 bloquea la acción y envía stderr al agente, y cualquier otro código de salida es un error no bloqueante escrito en el log de depuración.

Qué canal llega al agente según el código de salida. El camino discontinuo es el que todo el mundo da por hecho y no existe.

Exactamente la mitad pueden bloquear

Quince eventos detienen la acción con salida 2. Quince la ignoran y siguen adelante.

Pueden bloquear: PreToolUse, PermissionRequest, UserPromptSubmit, UserPromptExpansion, Stop, SubagentStop, TeammateIdle, TaskCreated, TaskCompleted, ConfigChange, PostToolBatch, PreCompact, Elicitation, ElicitationResult, WorktreeCreate.

No pueden bloquear: PostToolUse, PostToolUseFailure, PermissionDenied, StopFailure, Notification, SubagentStart, SessionStart, Setup, SessionEnd, CwdChanged, FileChanged, PostCompact, WorktreeRemove, InstructionsLoaded, MessageDisplay.

La consecuencia práctica: una barrera de seguridad va en PreToolUse, nunca en PostToolUse. PostToolUse se dispara después de que la herramienta haya tenido éxito. Salir con 2 ahí no deshace la escritura, solo imprime un error mientras el daño ya está en el disco. Si quieres detener un rm -rf, tienes exactamente un sitio donde hacerlo.

Que PostToolBatch bloquee mientras PostToolUse no lo hace merece una segunda mirada. Significa que una comprobación a nivel de lote todavía puede detener el turno después de que aterricen ediciones en paralelo: es lo más parecido a un veto posterior a la escritura que ofrece el sistema.

Los matchers son exactos, hasta que de repente dejan de serlo

El campo matcher cambia de estrategia de evaluación según sus propios caracteres, y nada te dice qué camino ha tomado.

MatcherEvaluado como
"*", "", u omitidocoincide con todo
Solo letras, dígitos, _, -, espacios, ,, |cadena exacta, o lista de cadenas exactas separadas por | o ,
Cualquier otra cosaexpresión regular de JavaScript sin anclar

La trampa está en «sin anclar». La referencia es explícita: la expresión se prueba con RegExp.prototype.test, que tiene éxito con una coincidencia en cualquier parte del valor. Así que Edit.* coincide con Edit y con NotebookEdit. Si querías una sola herramienta, escribe ^Edit$.

Dos comportamientos que dependen de la versión y conviene conocer antes de depurar lo que no es:

  • Los separadores por coma y la tolerancia a los espacios necesitan Claude Code v2.1.191 o posterior.
  • Los guiones entraron en el conjunto de caracteres de coincidencia exacta en v2.1.195. Antes de eso, un matcher como code-reviewer se trataba como una regex sin anclar, así que también se disparaba para senior-code-reviewer.

En AgentsRoom acotamos nuestro propio hook de atribución de archivos con Write|Edit|MultiEdit|NotebookEdit, que se queda en la vía de la cadena exacta y coincide con esas cuatro herramientas y con nada más. Los hooks de ciclo de vida que instalamos no llevan matcher alguno, porque siempre nos conciernen.

Seis sitios pueden definir hooks, y se fusionan

El instinto es buscar un orden de precedencia. No lo hay, y eso es justo lo interesante.

UbicaciónAlcance
~/.claude/settings.jsontodos tus proyectos, local a tu máquina
.claude/settings.jsonun proyecto, versionable
.claude/settings.local.jsonun proyecto, gitignorado por Claude Code
Managed policy settingstoda la organización, controlado por el administrador
hooks/hooks.json de un pluginmientras el plugin esté activado
Frontmatter de skill o de agentemientras el componente esté activo

De la referencia:

Las entradas de hook se fusionan entre los niveles de settings en lugar de reemplazarse: los settings de usuario, de proyecto y locales añaden sus propios hooks sin quitar los gestionados, y el ajuste disableAllHooks no puede desactivar hooks gestionados desde fuera de los settings gestionados.

Así que un hook de proyecto nunca reemplaza a uno global, se apila encima. Seis fuentes, todas aditivas. Un formateador PostToolUse definido en tus settings de usuario y otra vez en el proyecto se ejecuta dos veces por edición, y el único síntoma es la sensación de lentitud.

Diagrama que muestra las seis ubicaciones de settings de Claude Code que pueden definir hooks, todas fusionándose de forma aditiva en un único conjunto de hooks en lugar de reemplazarse entre sí.

Seis fuentes, un solo conjunto fusionado. Aquí nada reemplaza a nada.

Esto explica también por qué .claude/settings.local.json es el sitio adecuado para que una herramienta instale un hook en el proyecto de otra persona. Está acotado al proyecto, Claude Code lo gitignora y se carga sin ninguna opción de línea de comandos. Ahí es donde AgentsRoom escribe sus entradas, de modo que el .claude/settings.json versionado del usuario no se toca nunca y sus compañeros no heredan jamás una ruta específica de una máquina.

Lo que nos enseñó ejecutar hooks en producción

AgentsRoom instala hooks en cada proyecto que abre, para seguir el estado de los agentes de forma determinista y atribuir los archivos editados al agente correcto. Algunas cosas solo aparecen a esa escala.

Los nombres de evento desconocidos se ignoran en silencio. Esto no está en la documentación, y dependemos de ello. Cuando añadimos un evento de ciclo de vida nuevo a nuestro instalador, los usuarios con un CLI antiguo reciben un settings.local.json con un nombre de evento del que su binario no ha oído hablar jamás. Nada se rompe, nada avisa, la entrada se salta. Eso es lo que hace seguro publicar el instalador antes de una versión del CLI. Y es también, inevitablemente, por lo que una errata produce silencio total en vez de un error.

agent_id es lo que te dice que estás dentro de un subagente. El campo solo está presente cuando el hook se dispara dentro de una llamada a un subagente. Importa más de lo que parece: Stop se dispara cuando un subagente termina su turno, no solo el agente principal. Una regla ingenua del tipo «marcar la sesión como terminada en Stop» da toda la sesión por acabada la primera vez que vuelve cualquier subagente. Nos saltamos los eventos de fin de turno que llevan agent_id exactamente por esto.

No leas transcript_path para el turno en curso. La referencia avisa de que la transcripción se escribe de forma asíncrona y puede ir por detrás de la conversación en memoria, así que los mensajes más recientes pueden no estar todavía ahí cuando tu hook se dispara. Stop y SubagentStop reciben last_assistant_message precisamente para que nunca tengas que competir con el archivo.

Los hooks son la única señal de estado fiable. Antes de los hooks, raspábamos el PTY para averiguar si un agente estaba pensando, esperando o había terminado. Eso se rompe en cuanto el CLI se dibuja a través del buffer de pantalla alternativo del terminal, que es lo que hace /tui fullscreen. Los hooks se disparan igual bajo cualquier renderizador. Si estás construyendo algo que observe a un agente desde fuera, esta es la capa sobre la que construir, y el raspado se queda como plan B en el mejor de los casos.

async: true no cuesta nada. Un comando de hook puede declarar async: true, y el agente no lo espera. Nuestro hook hace un POST a un endpoint local con un tope de 2 segundos y devuelve el control; la latencia del turno del agente no se ve afectada ni siquiera cuando la aplicación receptora está cerrada. Si tu hook solo observa y nunca decide, ponlo en async y deja de pagarlo.

Nunca dejes que un hook escriba basura en el terminal. Nuestro script se traga todas las excepciones, incluso en el nivel superior. Un traceback sin capturar desde un hook no falla discretamente: imprime una traza de pila de Python en la sesión de terminal del usuario, en medio de su trabajo.

Tiempos de espera

Los valores por defecto son generosos, con tres excepciones que no lo son:

Tipo de hookTiempo de espera por defecto
command, http, mcp_tool600 s
prompt30 s
agent60 s
UserPromptSubmit (command, http, mcp_tool)30 s
MessageDisplay (command, http, mcp_tool)10 s
SessionEnd1,5 s compartidos entre todos los hooks, elevado para igualar un timeout por hook más largo, hasta 60 s

El presupuesto de SessionEnd es el que sorprende. Es un presupuesto compartido, no una asignación por hook, así que tres hooks de limpieza se reparten 1,5 segundos entre ellos salvo que lo subas explícitamente.

La versión corta

  • Existen 30 eventos. Seis son famosos.
  • stdout llega al agente solo en UserPromptSubmit, UserPromptExpansion y SessionStart. En todos los demás casos, usa salida 2 con stderr, o additionalContext en JSON.
  • 15 eventos bloquean con salida 2, 15 la ignoran. Las barreras de seguridad van en PreToolUse.
  • Los matchers son cadenas exactas hasta que un carácter especial los convierte en una regex sin anclar.
  • Seis fuentes de settings se fusionan de forma aditiva. Nada reemplaza a nada.
  • Un nombre de evento mal escrito falla en el silencio más absoluto.

Si prefieres ver estos eventos dispararse en vez de razonar sobre ellos, eso es lo que hemos construido: AgentsRoom muestra cada disparo de hook por agente, por proyecto, por ejecución, a través de decenas de agentes en paralelo y sesiones de subagentes. Los hooks que configuras en tus propios settings siguen funcionando exactamente como los escribiste, porque ejecuta el CLI real.

Seguir leyendo

Descargar AgentsRoom

Ejecuta tus agentes de IA (Claude, Codex, Antigravity CLI, OpenCode, Aider, Grok Build, Mistral Vibe, Kimi Code) en todos tus proyectos, desde una sola ventana.

GratisDescargar AgentsRoom

App complementaria: supervisa tus agentes en movimiento

Usa Claude, Codex, Antigravity CLI u otro proveedor de IA.

Instalar la extensión
Chrome Web Store

Envía bugs y peticiones directamente a tu backlog público.

Un vistazo a AgentsRoom en acción.

Multi-proyectos
Multi-proveedor
Multi-agentes
Estado en vivo
Diff y commit
App móvil
Vista previa
Equipos de agentes
Pruebas en navegador
Dev guiada por backlog
Biblioteca de prompts
Biblioteca de skills
Ver todas las funcionalidades