Ya sabes escribir un buen contrato para tu agente: le dices en el AGENTS.md cómo quieres que trabaje. El problema es que un LLM lee ese contrato y decide de nuevo en cada turno — así que la regla crítica se cumple casi siempre, y ese "casi" es justo el que te muerde. Los hooks son el peldaño siguiente: sacas la regla de la cabeza del modelo y la metes en la infraestructura, para que pase el 100% de las veces sin depender de su criterio. Te explico los eventos del ciclo de vida donde te enganchas, la anatomía del JSON, dónde vive cada hook y por qué importa, y te dejo tres hooks reales listos para pegar (auto-formateo, bloqueo de comandos peligrosos, notificación de escritorio) sacados de la documentación oficial de Anthropic. Un solo prompt maestro para montar tu primero con la seguridad puesta — porque un hook corre shell con tus permisos. Es el salto de "escribe el contrato" a "haz que el contrato se ejecute solo".
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.
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.
.env, un package-lock.json, algo dentro de .git/.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.
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.
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:
rm -rf o una edición a un archivo protegido.SessionStart con matcher compact) — alrededor de la compactación del contexto, para no perder lo importante cuando la memoria se comprime.Pre. Si quieres reaccionar a algo que ya pasó (formatear, avisar, registrar), engánchate a Post.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).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 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.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.
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.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.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.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).
.env, borrar recursivo, escribir en .git/): eso es un PreToolUse de bloqueo — respaldado por una regla de permisos para el candado duro.PostToolUse.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.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.
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:
.sh para los hooks de bloqueo, con su jq, su exit 2 y su mensaje a stderr.settings.json debe vivir.chmod +x del script, brew install jq si te falta.settings.json — tras leerlo y entenderlo. La red de seguridad no puede ser código que no revisaste.PreToolUse para impedir, PostToolUse/Notification para reaccionar.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.Únete a 4.200+ creadores. Sin tarjeta. Construye tu primera app con IA en minutos.