1. El momento · cuando descubres que "pedirle a la IA" no basta
Llega un punto en todo proyecto con IA en el que dejas de fiarte de la buena voluntad del modelo. Le escribiste en tu AGENTS.md que formatee el código, que no toque los archivos sensibles, que corra los tests. Y el 90% de las veces lo hace. Pero el 10% restante es justo el que te muerde: la vez que editó el .env de producción, la vez que se saltó el formato y el commit quedó sucio, la vez que ejecutó un rm -rf sobre la carpeta equivocada porque "parecía temporal".
El momento de los hooks es cuando piensas: "¿y si esto no dependiera de que el modelo decida hacerlo bien? ¿Y si simplemente pasara, siempre, sin depender de su criterio?". Esa es exactamente la promesa que la documentación oficial de Claude Code pone en el centro de la página de hooks: dan "control determinista" sobre el comportamiento del agente, "garantizando que ciertas acciones siempre ocurran en lugar de depender de que el LLM elija ejecutarlas" (traducción fiel del original en inglés). Esa frase es la tesis entera del recurso.
2. El dolor · de dónde sale y por qué pasa
El dolor no es un apocalipsis, es una erosión. Le pediste a la IA que hiciera algo "siempre" y a veces no lo hace, porque un LLM no es determinista: cada turno decide de nuevo, y en un turno cargado de contexto, tu recordatorio de formatear puede quedar sepultado bajo mil tokens de otra cosa. No es que la IA sea desobediente — es que le estás pidiendo consistencia a un sistema probabilístico. Es como pedirle a alguien brillante pero distraído que no olvide nunca cerrar la llave del gas: la mayoría de las noches la cierra, y esa es justo la razón por la que bajas la guardia.
- Repites la misma instrucción en cada sesión ("formatea", "no toques X", "corre los tests") y aún así a veces se salta.
- Descubriste después que la IA editó un archivo que jamás debía tocar:
.env, unpackage-lock.json, algo dentro de.git/. - Un commit se coló con formato inconsistente y ensució el diff de todo el equipo.
- Te da un poco de miedo dejar a la IA con permisos amplios porque no hay una red de seguridad que no dependa de ella misma.
- Tu AGENTS.md tiene reglas críticas escritas en prosa, y "escrito" no es lo mismo que "garantizado".
Y aquí está el ángulo que hace a este recurso avanzado: si ya leíste sobre escribir un buen contrato para tu agente (el AGENTS.md), los hooks son el siguiente peldaño. Pasas de "escribe el contrato" (declarativo: le dices qué quieres) a "haz que el contrato se ejecute solo" (determinista: la máquina lo cumple por ti). Es la diferencia entre una ley escrita en un papel y un policía parado en la esquina.
3. Qué es un hook (sin humo, en una frase)
Un hook es un comando de shell que Claude Code ejecuta automáticamente en un punto concreto de su ciclo de vida. Tú lo declaras una vez en un archivo settings.json; a partir de ahí, cada vez que ocurra ese punto del ciclo, tu comando corre. No lo dispara el modelo con su criterio: lo dispara el evento. Ahí está la magia — es determinista, no opinable.
4. Los eventos del ciclo de vida (los puntos donde te enganchas)
La documentación oficial lista muchos eventos del ciclo de vida — hay uno para casi cualquier momento del trabajo del agente. No te aprendas el número ni la lista completa: aprende la idea y quédate con los que de verdad vas a usar el 95% del tiempo. Estos son, con sus nombres reales tal cual aparecen en la doc:
- `SessionStart` — cuando arranca o se reanuda una sesión. Ideal para inyectar contexto fresco (ej. tus convenciones, los últimos commits). Su salida a stdout se añade al contexto de Claude.
- `UserPromptSubmit` — justo cuando envías un prompt, antes de que Claude lo procese. Puedes añadir contexto o incluso bloquear el prompt.
- `PreToolUse` — antes de que una herramienta se ejecute. Este puede bloquear la acción. Es tu guardaespaldas: aquí frenas un
rm -rfo una edición a un archivo protegido. - `PostToolUse` — después de que una herramienta tiene éxito. Aquí formateas el archivo recién editado, corres un lint, lo que quieras.
- `Notification` — cuando Claude te necesita (pide permiso o está esperando). Perfecto para una notificación de escritorio.
- `Stop` / `SubagentStop` — cuando Claude (o un subagente) termina de responder. Útil para un chequeo final antes de dar por cerrado el turno.
- `PreCompact` (y
SessionStartcon matchercompact) — alrededor de la compactación del contexto, para no perder lo importante cuando la memoria se comprime. - `SessionEnd` — cuando la sesión termina. Para limpiar archivos temporales, cerrar cosas.
Pre. Si quieres reaccionar a algo que ya pasó (formatear, avisar, registrar), engánchate a Post.5. Cómo se declara un hook (la anatomía del JSON)
Todo hook vive dentro de un bloque "hooks" en tu settings.json. La estructura es siempre la misma muñeca rusa: el nombre del evento → un matcher (a qué se aplica) → una lista de hooks con type: "command" y el command a correr. Míralo una vez y ya no se te olvida:
{
"hooks": {
"PostToolUse": [ // 1. el EVENTO del ciclo de vida
{
"matcher": "Edit|Write", // 2. A QUÉ herramientas aplica
"hooks": [
{
"type": "command", // 3. es un comando de shell
"command": "...tu comando..." // 4. QUÉ correr cuando ocurra
}
]
}
]
}
}PreToolUse, PostToolUse) filtra por nombre de herramienta: "Edit|Write" significa "solo cuando use Edit o Write", "Bash" solo comandos de shell. Un matcher vacío ("") se dispara siempre, para todo. La barra | separa alternativas (en versiones recientes de Claude Code también vale la coma: "Edit, Write" es equivalente).6. Dónde vive un hook (y por qué importa el lugar)
El lugar donde pones el hook define su alcance. Esto es clave: un hook de proyecto se comparte con tu equipo (se commitea al repo); uno global es solo tuyo. La tabla oficial resume dónde va cada cosa:
- `~/.claude/settings.json` — global, aplica a todos tus proyectos. No se comparte, es de tu máquina. Aquí van tus hooks personales (notificaciones de escritorio, por ejemplo).
- `.claude/settings.json` (dentro del repo) — solo ese proyecto, y se puede commitear: todo tu equipo hereda la regla. Aquí van las reglas del proyecto (formateo, archivos protegidos).
- `.claude/settings.local.json` — solo ese proyecto, privado: Claude Code lo gitignora cuando lo crea. Para tus ajustes locales que no quieres compartir.
.claude/settings.json y se commitea. Así el contrato deja de vivir en la cabeza de una persona y pasa a viajar con el código. Ese es el poder real: la disciplina se vuelve parte del repositorio, no un post-it que alguien tiene que recordar.7. Tres hooks copy-paste que valen su peso en oro
Basta de teoría. Aquí tienes tres hooks reales, sacados de la documentación oficial, listos para pegar. Empieza por el que más te duela hoy.
a) Auto-formatear después de cada edición (PostToolUse)
El clásico. Cada vez que Claude edita o escribe un archivo, se pasa Prettier automáticamente. Nunca más un commit con formato inconsistente. Va en .claude/settings.json de tu proyecto:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}jq -r '.tool_input.file_path' extrae la ruta del archivo editado de ese JSON, y xargs npx prettier --write se la pasa a Prettier. jq es un lector de JSON de línea de comandos — instálalo con brew install jq en macOS o apt-get install jq en Debian/Ubuntu.b) Bloquear comandos peligrosos como rm -rf (PreToolUse)
Este es el que te quita el miedo. Un hook PreToolUse que revisa cada comando de Bash antes de ejecutarlo y lo veta si contiene algo destructivo. Aquí sí importa un detalle técnico que la doc deja clarísimo: se bloquea saliendo con código 2, y lo que escribas a stderr le llega a Claude como explicación para que se corrija. Primero el script:
#!/bin/bash # .claude/hooks/block-rm-rf.sh INPUT=$(cat) COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command') if echo "$COMMAND" | grep -q "rm -rf"; then echo "Bloqueado: 'rm -rf' no está permitido. Borra rutas concretas, no recursivo-forzado." >&2 exit 2 # exit 2 = bloquea la accion; stderr le llega a Claude como feedback fi exit 0 # exit 0 = sin objecion; sigue el flujo normal de permisos
Hazlo ejecutable y regístralo en tus settings apuntando al script:
chmod +x .claude/hooks/block-rm-rf.sh
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm-rf.sh"
}
]
}
]
}
}grep -q "rm -rf" atrapa el caso obvio, pero se lo salta un rm -fr, un rm -rf con espacios raros, o un rm --recursive --force. Sirve de red visible y de feedback educativo para la IA, pero no lo confundas con un blindaje. La propia doc lo dice sin rodeos: el filtrado de hooks es best-effort y "falla abierto" (si no puede parsear el comando, lo deja pasar), así que para una prohibición DURA que nadie pueda saltarse, usa el sistema de permisos (permissions.deny), no un hook. El patrón correcto es: hook para reaccionar y avisar; permission rules para el candado real.PreToolUse que devuelve una decisión de bloqueo veta la herramienta INCLUSO en `bypassPermissions` o con `--dangerously-skip-permissions`. Es decir: aunque tú (o la IA) hayan bajado todas las barreras de permisos, tu hook de bloqueo sigue en pie. La regla es asimétrica y está pensada así a propósito: los hooks pueden endurecer la política, nunca aflojarla más allá de lo que permiten las reglas de permisos. Un hook de bloqueo es una regla que ni tú puedes saltarte por accidente.c) Notificación de escritorio cuando la IA te necesita (Notification)
Para dejar de vigilar la terminal. Cuando Claude termina o necesita tu permiso, te salta una notificación de escritorio y te vas a hacer otra cosa. Va en tu ~/.claude/settings.json global (ejemplo de macOS con osascript):
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code necesita tu atención\" with title \"Claude Code\"'"
}
]
}
]
}
}notify-send 'Claude Code' 'Claude Code necesita tu atención'; en Windows (PowerShell) un MessageBox de System.Windows.Forms. La estructura del JSON es idéntica — solo cambia la línea del command. Truco de macOS que documenta la propia guía: si no aparece la notificación, corre una vez osascript -e 'display notification "test"' y luego dale permiso a Script Editor en Ajustes del Sistema → Notificaciones.8. El hábito · cuándo pensar en un hook (y cuándo NO)
Los hooks no son para todo. La regla mental es sencilla: si algo debe pasar siempre, de forma mecánica y sin criterio, es un hook. Si algo requiere juicio caso por caso, no lo fuerces con un hook determinista — deja que lo decida el modelo (o usa los hooks de tipo prompt/agente que la doc también soporta, pero eso es otra liga).
- Cada vez que te descubras escribiendo "acuérdate de…" por tercera vez en tu AGENTS.md. Si lo repites, conviértelo en hook.
- Cuando haya una acción que jamás debe ocurrir (tocar
.env, borrar recursivo, escribir en.git/): eso es unPreToolUsede bloqueo — respaldado por una regla de permisos para el candado duro. - Cuando haya una acción que siempre debe ocurrir tras editar (formatear, ordenar imports, correr un lint): eso es un
PostToolUse. - Cuando quieras que un contexto crítico se re-inyecte tras cada compactación, para que la IA no "olvide" tus convenciones a mitad de sesión.
PostToolUse: no puede deshacer lo que ya pasó — solo reacciona. Para impedir, siempre PreToolUse. Meter demasiada lógica compleja en un hook lo vuelve frágil; mantenlos cortos y de un solo propósito.9. El protocolo de uso · monta tu primer hook útil
Aquí el detalle práctico: la propia doc te recomienda pedirle a Claude que te escriba el hook describiéndoselo. Pero para que salga bien —y sea seguro— conviene darle un encargo estructurado, no un "ponme un hook". Este es EL prompt maestro del recurso: uno solo, potente, que diseña tu primer hook con los frenos de seguridad puestos.
Quiero montar mi primer hook de Claude Code para automatizar una regla de mi flujo. La regla que quiero garantizar SIEMPRE es: [DESCRIBE LA REGLA, ej: "formatear con prettier cada archivo que edites" / "bloquear cualquier comando que borre archivos de forma recursiva" / "impedir que se edite el .env o cualquier cosa dentro de .git/" / "avisarme por notificación cuando termines"]. Antes de escribir nada, ayúdame a diseñarlo bien respondiendo esto en lenguaje claro (yo no programo): 1. EL EVENTO correcto del ciclo de vida para esta regla. Si la regla es IMPEDIR algo, debe ser PreToolUse (ocurre antes y puede bloquear). Si es REACCIONAR a algo ya hecho (formatear, avisar, registrar), debe ser PostToolUse o Notification. Dime cuál eliges y por qué. 2. EL MATCHER exacto (a qué herramientas aplica: Edit|Write, Bash, o vacío para todas) y por qué ese y no otro. Mantenlo lo más ESTRECHO posible: un matcher demasiado amplio dispara el hook donde no debe. 3. EL LUGAR donde debe vivir el settings.json: - Si la regla debe cumplirla todo el equipo en este repo → .claude/settings.json (se commitea). - Si es solo para mi máquina y todos mis proyectos → ~/.claude/settings.json. - Si es local y privada → .claude/settings.local.json. Dime cuál y por qué. 4. EL BLOQUE JSON completo, listo para pegar, con el hook ya montado. Si necesita un script aparte (típico en bloqueos con PreToolUse), dame también el script .sh completo y recuérdame hacerlo ejecutable con chmod +x. 5. SI ES UN HOOK DE BLOQUEO: úsalo con exit 2 y un mensaje claro a stderr que me explique por qué se bloqueó, para que tú mismo (Claude) recibas el motivo y te corrijas. No lo hagas silencioso. Y AVÍSAME si esta regla merece además una regla de permisos (permissions.deny) como candado duro, porque un grep en un hook es best-effort y se puede saltar. 6. SEGURIDAD, obligatorio: explícame en una frase QUÉ comando de shell va a correr este hook y confírmame que no hace nada destructivo ni envía datos a ningún lado. Recuérdame que un hook corre shell con mis permisos y que solo debo guardar comandos que entiendo. 7. CÓMO LO PRUEBO: dime cómo verificar que el hook está registrado (el comando /hooks dentro de Claude Code) y una forma de probar que dispara de verdad, sin romper nada real. Primero muéstrame SOLO el diseño (evento, matcher, lugar), luego el JSON y el script si hace falta, y al final la explicación de seguridad y la prueba. No modifiques mi settings.json todavía: quiero revisar el bloque antes de pegarlo yo.
Fíjate en el patrón: primero diseñas, revisas la seguridad, y tú pegas el bloque. Nunca dejas que un hook aparezca en tu configuración sin haberlo entendido. Es la misma disciplina de dos pasos que protege cualquier automatización potente: la máquina propone, tú apruebas.
10. Los caminos más fáciles · qué haces por chat y qué por archivo
Para que no se te enrede, esto es lo que puedes delegar a la IA por el chat y lo que sigue siendo cosa tuya:
- Escribirte el bloque JSON del hook completo si le describes la regla (la doc oficial lo recomienda explícitamente).
- Generarte el script
.shpara los hooks de bloqueo, con sujq, suexit 2y su mensaje a stderr. - Explicarte qué evento y qué matcher encajan con tu regla, y en qué
settings.jsondebe vivir. - Recordarte los pasos de instalación:
chmod +xdel script,brew install jqsi te falta.
- Pegar el bloque en tu
settings.json— tras leerlo y entenderlo. La red de seguridad no puede ser código que no revisaste. - Elegir el lugar (repo compartido vs. global vs. local): eso define quién hereda la regla.
- Confirmar que el comando del hook es seguro (corre shell con tus permisos: si no lo entiendes, no lo guardas).
- Verificar con `/hooks` dentro de Claude Code que quedó registrado, y probar que dispara sin romper nada real.
Resumen · tu checklist de hooks
- Elegí el evento correcto:
PreToolUsepara impedir,PostToolUse/Notificationpara reaccionar. - El matcher es lo más estrecho posible (no dispara donde no debe).
- Está en el lugar correcto: repo compartido, global tuyo, o local privado.
- Si bloquea, usa `exit 2` con un mensaje claro a stderr para que la IA se corrija — y si es una prohibición dura, la respaldé con una regla de permisos.
- Entiendo el comando que corre (shell con mis permisos): no guardé nada que no revisé.
- Lo verifiqué con `/hooks` y probé que dispara sin romper nada real.
- Saqué la regla de la prosa de mi AGENTS.md y la convertí en algo que pasa siempre.
PreToolUse que veta lo que no debe entrar. Es la misma filosofía de un hook, aplicada a un equipo entero en vez de a una terminal: que la calidad no sea una promesa que alguien recuerda, sino un sensor en la cinta que no se puede saltar. Tú pones la regla una vez; el sistema la cumple para siempre.