diffox
Blog

¿Qué es un hook y cómo se escribe para agentes de código?

El agente no siempre cumple lo que pones en el archivo de reglas; un hook se ejecuta siempre. Cómo escribirlo en cinco herramientas.

CompartirXLinkedIn

Resumen. Un hook es un comando que escribes tú y que el agente de programación ejecuta por su cuenta en momentos concretos. Que se ejecute o no, no depende de lo que decida el agente; por eso se usa para las reglas que hay que cumplir sí o sí. Lo tienen Claude Code, Codex, Cursor, Gemini CLI y GitHub Copilot. Comprobamos cada dato el 18 de octubre de 2026 en la documentación de cada herramienta; los ejemplos salen del repositorio que usamos a diario. El 28 de octubre de 2026 añadimos Antigravity CLI, de Google.

  • El archivo de reglas no basta por sí solo. La regla que escribes en AGENTS.md o en CLAUDE.md es información para el agente, no una garantía de que la cumpla. Las cinco herramientas tienen un hook que se ejecuta antes de que el agente lance un comando.
  • Todas se detienen con el mismo código, pero no tratan igual los errores. Si el hook sale con el código 2, las cinco herramientas detienen la acción. Con otro código de error, como el 1, Claude Code, Cursor y Gemini CLI siguen adelante, y Copilot la detiene. La documentación de Codex no lo dice.
  • Un hook que se cuelga también desactiva la regla. Si el hook agota el tiempo de espera, Claude Code, Cursor y Copilot siguen adelante con la acción. Puedes cambiarlo con el ajuste onFailure en Claude Code y con failClosed en Cursor. La documentación de Codex y la de Gemini CLI no lo dicen.
  • En nuestro repositorio usamos tres hooks. Uno informa del estado del directorio al abrir la sesión, otro detiene un merge hecho sin aprobación y el tercero avisa al agente cuando cambia la rama.

¿Por qué no basta con el archivo de reglas?

5herramientas, revisadas en su propia documentación
2es el código de salida que detiene la acción en las cinco
33eventos a los que se puede conectar un hook (Claude Code)
3hooks en uso en nuestro propio repositorio

Hay dos formas de darle una regla a un agente de programación. La primera es el archivo de reglas: escribes «pregúntame antes de hacer merge» en AGENTS.md, CLAUDE.md o GEMINI.md. La segunda es el hook: conviertes esa misma regla en un pequeño script que el agente ejecuta por su cuenta en un momento concreto.

Donde mejor se explica la diferencia es en la documentación de Claude Code: «Claude treats them as context, not enforced configuration. To block an action regardless of what Claude decides, use a PreToolUse hook instead.» Es decir, el archivo de reglas es solo información para el agente, y el agente no siempre la tiene en cuenta. Si quieres detener una acción decida lo que decida el agente, usas un hook.

A 18 de octubre de 2026, hay hooks en cinco herramientas de programación: Claude Code, Codex de OpenAI, Cursor, Gemini CLI de Google y GitHub Copilot (la CLI y el agente en la nube). En todas funcionan igual: el hook se conecta a un evento, recibe los detalles del evento en JSON y, si hace falta, detiene un comando antes de que se ejecute.

Los ejemplos salen del repositorio en el que desarrollamos diffox y que usamos a diario. Allí trabajamos con Claude Code; lo que contamos no es una función del producto, sino la forma de trabajar del equipo.

¿Dónde se definen los hooks en cada herramienta?

En todas las herramientas, los hooks se escriben en un archivo de configuración; de una a otra cambia sobre todo el nombre del archivo y el del evento. Para poner reglas, el evento más usado es el que se dispara justo antes de que el agente use una herramienta (ejecutar un comando, editar un archivo):

Hooks en cinco herramientas de programación: archivo de configuración, evento que se dispara antes de usar una herramienta y forma de detener la acción
HerramientaArchivo de configuración (proyecto · usuario)Antes de usar una herramientaPara detener la acción
Claude Code.claude/settings.json · ~/.claude/settings.jsonPreToolUseexit 2 o permissionDecision: "deny"
Codex.codex/hooks.json · ~/.codex/hooks.jsonPreToolUseexit 2 o permissionDecision: "deny"
Cursor.cursor/hooks.json · ~/.cursor/hooks.jsonpreToolUse; para comandos de shell, beforeShellExecutionexit 2 o permission: "deny"
Gemini CLI.gemini/settings.json · ~/.gemini/settings.jsonBeforeToolexit 2 o decision: "deny"
GitHub Copilot.github/hooks/*.json · ~/.copilot/hooks/*.jsonpreToolUseexit 2 o permissionDecision: "deny"

Fuente: la documentación de hooks de cada herramienta, 18 de octubre de 2026. La decisión en JSON se toma con lo que el hook imprime en la salida estándar (stdout). Codex también lee la misma configuración dentro de config.toml. En Copilot, el archivo de usuario solo vale para la CLI; el agente en la nube lee el archivo del repositorio desde la rama por defecto.

Cuando se produce el evento, tu hook recibe los detalles en JSON: qué herramienta se llamó y con qué entrada. En Claude Code, la entrada que llega antes de un comando Bash tiene este aspecto:

EJEMPLO. El hook recibe un objeto JSON. Claude Code · stdin · PreToolUse: { /   "session_id": "abc123", /   "cwd": "/home/dev/proyecto", /   "hook_event_name": "PreToolUse", /   "tool_name": "Bash", /   "tool_input": { /     "command": "gh pr merge 42 --squash" /   } / }. tool_name: Qué herramienta: el matcher mira este nombre. tool_input.command: El comando que se va a ejecutar. Código de salida: La decisión del script: el 2 detiene.
Ejemplo abreviado del JSON que recibe, en Claude Code, un hook PreToolUse que se ejecuta antes de la herramienta Bash. Nombres de los campos según la documentación de hooks de Claude Code, 18 de octubre de 2026; en otras herramientas, los campos se llaman de otra forma.

Las herramientas también leen los archivos de las demás: Copilot lee además el archivo .claude/settings.json del repositorio, Cursor puede cargar hooks de Claude Code y Gemini CLI define la variable CLAUDE_PROJECT_DIR para que no se rompan los scripts escritos para Claude Code. Así que puedes escribir un hook una vez y usarlo en varias herramientas. Aun así, como los nombres de los eventos y los campos de salida cambian, prueba el hook en cada herramienta por separado.

El 18 de junio de 2026, Google dejó de dar servicio en Gemini CLI a los usuarios gratuitos y a los suscriptores de Google AI Pro y Ultra, y los dirigió a Antigravity CLI; con una clave de API de pago o una licencia de empresa puedes seguir usando Gemini CLI. Según el anuncio de Google, Antigravity CLI también tiene hooks; leímos su documentación el 28 de octubre de 2026. Los hooks se escriben en .agents/hooks.json para un proyecto y en ~/.gemini/config/hooks.json para el usuario. El evento que se dispara antes de usar una herramienta se llama PreToolUse, como en Claude Code. Para detener la acción, el hook devuelve decision: "deny" en el JSON. El tiempo de espera es de 30 segundos por defecto; la documentación no dice qué hacen los códigos de salida.

¿Cómo detiene un hook una acción?

El hook comunica su decisión de dos formas: con el código de salida o con el JSON que imprime. En las cinco herramientas, si el hook sale con el código 2, la acción se detiene y el agente recibe la explicación que el hook escribió en la salida de errores (stderr). Lo que cambia es qué hace cada herramienta cuando el hook sale con otro código de error o agota el tiempo de espera:

Qué pasa si un hook que se ejecuta antes de usar una herramienta sale con un código de error distinto de 2 o agota el tiempo de espera
HerramientaCódigo de error distinto de 2Tiempo de espera agotadoAjuste que detiene la acción
Claude CodeLa acción sigueLa acción sigue"onFailure": "block"
CodexLa documentación no lo diceLa documentación no lo dice—
CursorLa acción sigueLa acción sigue"failClosed": true
Gemini CLIMuestra un aviso y la acción sigueLa documentación no lo dice—
GitHub CopilotLa acción se detieneLa acción sigue—

Fuente: la documentación de hooks de cada herramienta, 18 de octubre de 2026. «La documentación no lo dice»: en las páginas que leímos ese día no encontramos ninguna frase que describa este caso. El ajuste de Claude Code existe desde la versión 2.1.295. En Windows sin Git Bash, un hook de comando PreToolUse en forma de shell que falla puede detener la acción incluso sin onFailure.

Fíjate primero en la primera columna. En Unix, el código de error habitual es el 1. Claude Code, Cursor y Gemini CLI lo tratan como un error que no detiene la acción; Copilot, en cambio, la detiene con ese mismo código. Por eso la documentación de Claude Code pide usar exit 2 en los hooks que imponen una regla. Gemini CLI, por su parte, recomienda salir con 0 y escribir JSON incluso cuando se detiene la acción a propósito; si el hook imprime algo que no es JSON, deja pasar la acción.

El problema de la segunda columna cuesta más verlo: si la ruta del hook está mal o el script se cuelga, es como si la regla no existiera. Donde la acción tenga que detenerse sí o sí, usa el ajuste onFailure en Claude Code y failClosed en Cursor.

El ejemplo de abajo es una versión simplificada, para Claude Code, del control de merge de nuestro repositorio. Primero, el archivo de configuración:

.claude/settings.json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/merge-gate.sh"
          }
        ]
      }
    ]
  }
}

Después, el script:

.claude/hooks/merge-gate.sh
#!/bin/bash
# La entrada llega por stdin en JSON. Si no se puede leer, la acción se detiene.
COMMAND=$(jq -r '.tool_input.command // empty') || {
  echo "El hook no pudo leer la entrada; acción detenida." >&2
  exit 2
}

case "$COMMAND" in
  *"MERGE_APPROVED=1 gh pr merge"*) exit 0 ;;  # la aprobación va en el comando
  *"gh pr merge"*)
    echo "El merge lo decide el dueño del proyecto. Pregunta antes." >&2
    exit 2 ;;                                 # la acción se detiene
esac
exit 0                                        # sin objeciones

Cuando el script sale con 2, Claude Code no ejecuta el comando y le pasa al agente la frase que escribiste en la salida de errores. También puedes tomar la misma decisión saliendo con 0 y escribiendo JSON: en el campo permissionDecision pones deny (rechazar), ask (preguntar) o allow (permitir). Los hooks PreToolUse se ejecutan antes que los ajustes de permisos; una decisión deny vale incluso en el modo bypassPermissions, que se salta todas las preguntas de permiso. Codex todavía no admite el valor ask: da el hook por fallido y la acción sigue adelante.

¿Qué hooks usamos en nuestro repositorio?

El archivo .claude/settings.json de nuestro repositorio tiene 3 hooks. Escribimos cada uno después de un error que nos pasó, para que no se repitiera.

Los tres hooks de nuestro repositorio. Uno informa, otro detiene y otro avisa. El cuarto lo ejecuta git. SessionStart Estado del directorio: Dice la rama, los archivos sin commit y los worktrees. PreToolUse · Bash Control de merge: Sin aprobación, detiene el comando de merge. PreToolUse · Edit|Write Aviso de cambio de rama: Avisa al agente si cambió la rama o el commit. git · pre-push Hook de git: Va antes del push; no lo lanza el agente, sino git.
Los tres hooks de Claude Code de nuestro repositorio y un hook de git. El tercero se ejecuta antes de las herramientas Edit|Write|NotebookEdit. Fuente: el archivo de configuración y los scripts de hooks del repositorio.

Al abrir la sesión. Cuando empieza una sesión, el hook SessionStart dice la rama actual, cuántos archivos tienen cambios sin commit y qué worktrees hay abiertos, y luego da un veredicto: «puedes trabajar en este directorio» o «abre antes un worktree aparte». Esa regla ya estaba escrita en CLAUDE.md. Pero una sesión la leyó, no la aplicó a su propia situación y planificó cambios en un directorio que cambió de rama tres veces en cuarenta minutos. Una regla que depende de que el agente se acuerde no protege; el hook hace la pregunta en lugar del agente.

Control de merge. En nuestro repositorio no hacemos merge de un pull request sin la aprobación del dueño del proyecto. Pero un agente, cuando pasan los tests, puede lanzar gh pr merge como siguiente paso. El hook PreToolUse que se ejecuta antes de la herramienta Bash detiene ese comando con el código 2 y le dice al agente que pida antes la aprobación. Cuando llega la aprobación, el comando se vuelve a ejecutar con una variable de entorno delante; así, la aprobación queda en el registro de la sesión, justo al lado del merge. Si el hook no puede leer la entrada, también detiene la acción: su primera versión salía con 0 y dejaba pasar los comandos que no podía leer.

Aviso de cambio de rama. El veredicto del inicio de sesión describe la situación de ese momento y puede quedar desfasado: si hay dos sesiones abiertas en la misma rama y una pasa a otra rama, cambia el sitio donde trabaja la otra. El segundo hook PreToolUse se ejecuta antes de las herramientas que editan archivos y, antes de cada edición, compara la rama y el commit con lo último que se le dijo a la sesión. Si no coinciden, le manda al agente una nota que empieza con esta línea: «drift: this checkout MOVED since the session was last told what it is». No detiene la acción; pasa la nota en el campo additionalContext y sale con 0.

Un hook de git es otra cosa. En el mismo repositorio hay también un hook pre-push: impide hacer push directamente a las ramas protegidas. No lo ejecuta el agente de programación, sino git; por eso alcanza a cualquiera que haga push desde ese clon, también a los agentes, usen la herramienta que usen. Los dos tipos se ejecutan solo en tu máquina: el pre-push se puede saltar con --no-verify, así que no sustituye a los controles del lado del servidor.

En qué se diferencian un hook, una skill y el archivo de reglas

Los tres deciden cómo se comporta el agente, pero entran en juego en momentos distintos. Esta es la comparación de la documentación de Claude Code:

CLAUDE.md, skill y hook: cuándo entran en juego, si se aplican cada vez, cuánto ocupan en el contexto y para qué se usan
CLAUDE.mdSkillHook
Cuándo entra en juegoAl principio de cada sesiónCuando escribes /nombre o Claude ve que encaja con la tareaCada vez que ocurre su evento
¿Se aplica cada vez?No, es información para el agenteNo, Claude la interpretaSí, se ejecuta cada vez que ocurre el evento
Lo que ocupa en el contextoTodo, en cada peticiónLa descripción, en cada sesión; todo, cuando se usaNada, salvo que devuelva una salida
Para qué se usaReglas de «haz siempre esto», comandosFlujos de trabajo que se repiten, material de consultaFormatear, detener comandos peligrosos, notificaciones

Fuente: documentación de Claude Code, página «Extend Claude Code», 18 de octubre de 2026.

En resumen: si el agente tiene que decidir cómo aplicar los pasos, usa una skill; si la acción tiene que hacerse siempre igual, un hook. La lista de comprobación para publicar una versión es una skill; formatear cada archivo que se guarda es un hook. Como dice la misma página, una instrucción en el archivo de reglas o en una skill es una petición, no una garantía. Las reglas que no tienen vuelta atrás si se incumplen (un push a la rama equivocada, un merge antes de tiempo) no las dejes solo en manos de una instrucción.

Los hooks no se definen solo en el archivo de configuración: las skills y los subagentes también pueden llevar hooks en su propia definición. En Claude Code, el hook de un subagente solo vale mientras ese subagente está en marcha; el de una skill, en cambio, sigue ejecutándose hasta el final de la sesión una vez que se ha llamado a la skill. En Copilot, el hook de un agente personalizado solo se ejecuta mientras ese agente está seleccionado.

No conviertas todas las reglas en hooks

Un hook se ejecuta cada vez, y un hook mal escrito también falla cada vez. Antes de escribir uno, ten en cuenta esto:

  • Deja en el archivo de reglas la información que no cambia. La documentación de Claude Code recomienda CLAUDE.md para los datos fijos. Un hook encaja mejor para pasar el estado del momento (en qué rama estás, cuál fue el último resultado de la CI).
  • No dejes en manos de un hook las decisiones que piden criterio. Una simple comprobación de texto no puede responder «¿este cambio necesita tests?»; un paso así se escribe en una skill.
  • No detengas el trabajo sin motivo. De nuestros tres hooks, dos solo informan y no detienen la acción. Un hook que detiene el trabajo sin razón acaba desactivado en poco tiempo, en vez de corregido.
  • Un hook que busca texto no es una medida de seguridad. Nuestro control de merge no detecta un comando de merge escrito dentro de una variable o de un eval. Detiene al agente que se olvida de preguntar, no al que intenta saltarse la regla a propósito. La documentación de Codex también describe los hooks como «a useful guardrail, not a complete enforcement boundary». Para permisos y prohibiciones estrictos, la documentación de Claude Code recomienda las reglas de permisos.
  • Revisa como si fuera código un hook que escribió otra persona. Los hooks de comando se ejecutan con tus permisos de usuario. Codex no ejecuta un hook que llega del repositorio hasta que lo revisas y lo apruebas; Cursor solo ejecuta los hooks del proyecto en espacios de trabajo de confianza, y Gemini CLI trata un hook modificado como un hook nuevo y no confiable.

Los hooks se ejecutan en tu máquina, antes de la acción; no miran si el código escrito es correcto. Eso le toca a la revisión de código: cuando diffox revisa un pull request, lee también el archivo de reglas del repositorio (AGENTS.md o, si no existe, CLAUDE.md) y usa como reglas de revisión las frases que dicen qué hay que hacer.

Preguntas frecuentes

¿Qué es un hook y qué herramientas de programación lo tienen?

Un hook es un comando que escribes tú y que el agente de programación ejecuta por su cuenta en momentos concretos. A 18 de octubre de 2026, lo tienen Claude Code, Codex, Cursor, Gemini CLI y GitHub Copilot. En el archivo de configuración indicas qué comando se ejecuta en cada evento. Como su ejecución no depende de lo que decida el agente, la regla se aplica cada vez.

¿Cómo detiene un hook un comando en Claude Code?

Si un hook conectado al evento PreToolUse sale con el código 2, Claude Code no ejecuta el comando y le pasa al agente la explicación que el hook escribió en la salida de errores. Salir con 0 significa «no tengo objeciones»; otro código, como el 1, cuenta como un error que no detiene la acción. El hook también puede salir con 0 y escribir JSON: el campo permissionDecision toma el valor deny, ask o allow.

¿Cómo se usan los hooks en Cursor?

Los hooks se escriben en .cursor/hooks.json para el proyecto y en ~/.cursor/hooks.json para el usuario. preToolUse se ejecuta antes de que el agente use una herramienta, y beforeShellExecution, antes de que ejecute un comando de shell. Si el hook sale con 2 o devuelve permission: "deny" en el JSON, la acción se detiene. Con otro código de error, la acción sigue; puedes cambiarlo con failClosed: true. Los hooks del proyecto solo se ejecutan en espacios de trabajo de confianza.

¿Codex y Gemini CLI tienen hooks?

Sí. En Codex, los hooks se escriben en .codex/hooks.json o en config.toml y vienen activados por defecto; Codex no ejecuta un hook que llega del repositorio hasta que lo revisas y lo apruebas. En Gemini CLI, los hooks van en settings.json, y el evento que se dispara antes de usar una herramienta se llama BeforeTool. En los dos, si el hook sale con el código 2, la acción se detiene.

¿Cómo se define un hook en GitHub Copilot?

Pones un archivo JSON en la carpeta .github/hooks/ del repositorio o, para uso personal en Copilot CLI, en ~/.copilot/hooks/. El archivo lleva version: 1 y un objeto hooks. Antes de usar una herramienta se ejecuta preToolUse; si el hook imprime permissionDecision: "deny" o sale con 2, la acción se detiene. El agente en la nube lee el archivo de hooks de la rama por defecto del repositorio.

¿Qué pasa si un hook falla o agota el tiempo de espera?

En la mayoría de las herramientas, la acción sigue adelante. En Claude Code, un hook que da error o agota el tiempo de espera no detiene la acción; en Cursor, un hook que falla también la deja pasar, y en Copilot un tiempo de espera agotado deja pasar la acción en todos los casos. Para cambiarlo existen los ajustes "onFailure": "block" en Claude Code y "failClosed": true en Cursor. En Copilot, en cambio, si un hook preToolUse sale con un código de error distinto de 2, la acción se detiene.

¿Qué diferencia hay entre un hook y una skill?

Un hook es un comando que la herramienta ejecuta cuando ocurre un evento concreto, y se ejecuta cada vez. Una skill, en cambio, es un archivo de instrucciones que el agente lee y aplica (en Claude Code, SKILL.md): se carga cuando la llamas por su nombre o cuando el agente ve que encaja con la tarea, y es el agente quien decide qué hacer. Si la acción tiene que hacerse siempre igual, usa un hook; si el agente tiene que adaptar los pasos a la situación, una skill.