Los hooks de Claude Code explicados: la capa determinista en torno a tu agente
¿Qué son los ganchos de Claude Code? Los ganchos son comandos de shell definidos por el usuario (además de endpoints HTTP, herramientas de MCP y prompts para el 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 una sesión, cuando Claude termina de responder.1 Mientras que CLAUDE.md le da al modelo instrucciones que probablemente seguirá, los ganchos se ejecutan coopere o no el modelo. Escribe /hooks dentro de cualquier sesión para ver todos los eventos del ciclo de vida y qué hay conectado a cada uno.
{.answer-block}
La mayoría de los desarrolladores usan Claude Code con dos capas de control: los permisos, que regulan lo que el agente puede hacer, y CLAUDE.md, que describe lo que debería hacer. Los ganchos son la tercera capa, y la única que garantiza algo. A continuación: el modelo mental, cada evento del ciclo de vida en la documentación actual, el contrato exacto de entrada/salida, la configuración, cinco patrones que funcionan y un marco para decidir. Cada detalle de la API se verificó contra la referencia y la guía oficiales de ganchos al 1 de julio de 2026; este sistema evoluciona rápido, así que cuando este artículo y la referencia discrepen, gana la referencia. (¿Recién llegas a Claude Code? Empieza con la configuración en 5 minutos o la ruta para quienes empiezan con Claude Code.)
TL;DR: los ganchos reciben JSON por stdin y responden con códigos de salida o JSON por stdout. El código de salida 0 permite, el 2 bloquea (en los eventos que admiten bloqueo) y el 1 —el código de fallo convencional en Unix— no bloquea nada, que es la mayor trampa de los ganchos.2 Se configuran en settings.json bajo nombres de evento como PreToolUse y Stop, filtrados por matchers. Usa ganchos 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 formulación cae de otra manera. CLAUDE.md, las skills y los prompts son todos sugerencias: de alta calidad, casi siempre seguidas, nunca garantizadas.
Los ganchos son la envoltura determinista alrededor de ese núcleo. La definición oficial: «comandos de shell definidos por el usuario, endpoints HTTP o prompts para el LLM que se ejecutan automáticamente en puntos específicos del ciclo de vida de Claude Code», que brindan «control determinista sobre el comportamiento de Claude Code, garantizando que ciertas acciones siempre ocurran en lugar de depender de que el LLM decida ejecutarlas».3 El formateador se dispara en cada edición. La protección de comandos evalúa cada llamada a Bash. La puerta de finalización revisa cada cierre.
La aplicación es real, no cosmética: los ganchos de PreToolUse se disparan antes de cualquier comprobación del modo de permisos, de modo que un gancho que devuelve permissionDecision: "deny" bloquea la herramienta incluso en modo bypassPermissions o bajo --dangerously-skip-permissions. Lo contrario no se cumple: un gancho que devuelve "allow" no puede relajar las reglas de denegación definidas en la configuración. Los ganchos pueden endurecer la política más allá de lo que permiten los permisos, nunca debilitarla.4
El ciclo de vida: todos los eventos de gancho
Al 1 de julio de 2026, la referencia documenta 30 eventos de gancho.1 Se agrupan 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 específicas: cambios de configuración, compactación, subagentes, interacciones de 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 corra 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 expansiones de skills/comandos |
PreToolUse |
Antes de que se ejecute una llamada a una herramienta | Bloquear comandos de shell destructivos |
PermissionRequest |
Aparece un diálogo de permiso | Aprobar automáticamente comandos de confianza para que no te pregunte |
PermissionDenied |
El clasificador del modo automático deniega una llamada a una herramienta | Devolver retry: true para que el modelo pueda reintentar |
PostToolUse |
Después de que una llamada a una herramienta tiene éxito | Formatear automáticamente cada archivo editado |
PostToolUseFailure |
Después de que una llamada a una herramienta falla | Registrar comandos fallidos para su análisis |
PostToolBatch |
Después de un lote de llamadas paralelas a herramientas, antes de la siguiente llamada al modelo | Crear un punto de control o detener el bucle agéntico |
Notification |
Claude Code envía una notificación | Alerta de escritorio cuando Claude necesita datos |
MessageDisplay |
Mientras se muestra el texto del mensaje del asistente | Censurar en pantalla (solo la visualización; la transcripción no cambia) |
SubagentStart |
Se genera un subagente | Inyectar contexto específico 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 |
Imponer reglas de nomenclatura o alcance de las tareas |
TaskCompleted |
Una tarea se marca como completada | Verificar los criterios de aceptación antes de dar por firme la finalización |
Stop |
Claude termina de responder | Puerta de finalización: impedir el cierre hasta que pasen las pruebas |
StopFailure |
El turno termina por un error de la API | Alertar ante rate_limit o billing_error (solo registro; la salida se ignora) |
TeammateIdle |
Un compañero del equipo de agentes está por quedar inactivo | Mantener a los compañeros trabajando en una cola |
InstructionsLoaded |
Un archivo CLAUDE.md o .claude/rules/*.md se carga en el contexto |
Registrar qué instrucciones entraron en la sesión |
ConfigChange |
Un archivo de configuración cambia a mitad de la sesión | Bloquear ediciones no autorizadas de la configuración |
CwdChanged |
El directorio de trabajo cambia | Recargar entornos al estilo direnv |
FileChanged |
Un archivo vigilado cambia en el disco | Refrescar las variables de entorno cuando .env cambia |
WorktreeCreate |
Se crea un worktree mediante --worktree o isolation: "worktree" |
Reemplazar el aprovisionamiento predeterminado de worktrees de git |
WorktreeRemove |
Se elimina un worktree | Limpieza personalizada al salir de la sesión o del subagente |
PreCompact |
Antes de la compactación del contexto | Guardar el estado que no puedes permitirte perder |
PostCompact |
Después de que la compactación termina | Reinyectar contexto crítico |
Elicitation |
Un servidor de MCP solicita datos del usuario | Rellenar formularios automáticamente en ejecuciones headless |
ElicitationResult |
Después de que respondes una solicitud de MCP | Validar o anular la respuesta antes de que se devuelva |
SessionEnd |
La sesión termina | Archivar registros, liberar recursos |
No vas a necesitar la mayoría de estos. Casi toda configuración de producción se construye a partir de 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 ganchos de comando reciben JSON por stdin y responden mediante códigos de salida, stdout y stderr. (Los ganchos HTTP reciben el mismo JSON como cuerpo de un POST y responden mediante el cuerpo de la respuesta).2
Cada evento entrega un envoltorio común —session_id, transcript_path, cwd y hook_event_name, con permission_mode en la mayoría de los eventos— además de campos específicos de cada evento. Un gancho de PreToolUse para un comando 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 la parte final: UserPromptSubmit lleva prompt, SessionStart lleva source (startup/resume/clear/compact), Stop lleva stop_hook_active y last_assistant_message. Los ganchos disparados dentro de subagentes reciben además agent_id y agent_type.2
Códigos de salida
Tres resultados:2
- Código de salida 0: éxito. Claude Code analiza stdout en busca de campos de salida JSON. En la mayoría de los eventos, stdout va solo al registro de depuración; para
UserPromptSubmit,UserPromptExpansionySessionStart, el stdout plano se añade como contexto que Claude puede ver. - Código de salida 2: error de bloqueo. Se ignora stdout (incluido cualquier JSON); stderr se devuelve a Claude como el 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 de salida 1 no bloquea nada. La documentación lo advierte directamente: Claude Code trata el código de salida 1 como un error no bloqueante y sigue adelante, aunque 1 sea el código de fallo convencional en Unix. Los ganchos de política deben usar exit 2.2
Lo que hace el código de salida 2, según el evento:2
| Evento | Efecto del código de salida 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 el cierre; 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 (excepto policy_settings) |
PreCompact |
Bloquea la compactación |
PostToolBatch |
Detiene el bucle agéntico antes de la siguiente llamada al modelo |
Elicitation / ElicitationResult |
Deniega la solicitud / convierte la respuesta en un rechazo |
WorktreeCreate |
Cualquier salida distinta de cero aborta la creación del worktree |
Todo lo demás no puede bloquear. PostToolUse y PostToolUseFailure muestran stderr a Claude (la herramienta ya se ejecutó); SessionStart, Notification, SessionEnd, CwdChanged, FileChanged, PostCompact, SubagentStart y Setup muestran stderr solo al usuario; StopFailure, InstructionsLoaded, MessageDisplay y PermissionDenied ignoran el código de salida; para PermissionDenied la única palanca es el JSON retry: true.2
Salida JSON
Para un control más fino que bloquear-o-callar, sal con código 0 e imprime un objeto JSON en stdout. Una regla por adelantado: códigos de salida o JSON, nunca ambos; el JSON solo se procesa con el código de salida 0, y el 2 lo descarta.5
Los campos universales funcionan en todos los eventos: continue: false detiene a Claude por completo (con stopReason mostrado al usuario), suppressOutput oculta stdout de la transcripción, systemMessage le muestra una advertencia al usuario y terminalSequence emite una secuencia de escape de terminal permitida (notificación de escritorio, título de la ventana, campana). Los campos de decisión son específicos de cada evento:5
| Eventos | Patrón de decisión | Campos clave |
|---|---|---|
UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact |
decision de nivel superior |
decision: "block" + reason (mostrado a Claude). Omite decision para permitir |
PreToolUse |
hookSpecificOutput |
permissionDecision: "allow" | "deny" | "ask" | "defer", además de permissionDecisionReason y updatedInput para reescribir los argumentos de la herramienta antes de ejecutarla |
PermissionRequest |
hookSpecificOutput |
decision.behavior: "allow" | "deny", opcionalmente decision.updatedInput |
PermissionDenied |
hookSpecificOutput |
retry: true le indica al modelo que puede reintentar |
PostToolUse |
hookSpecificOutput |
updatedToolOutput reemplaza el resultado de la herramienta |
Stop / SubagentStop |
hookSpecificOutput |
additionalContext: retroalimentación que no es error y que continúa la conversación sin contar como error de gancho |
SessionStart, Setup, SubagentStart |
Solo contexto | additionalContext, además de initialUserMessage, sessionTitle, watchPaths, reloadSkills exclusivos de SessionStart. Sin bloqueo |
MessageDisplay |
hookSpecificOutput |
displayContent reemplaza solo el texto en pantalla |
Elicitation / ElicitationResult |
hookSpecificOutput |
action: "accept" | "decline" | "cancel", además de content |
WorktreeRemove, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, FileChanged |
Ninguno | Solo efectos secundarios |
Dos detalles que hacen tropezar a la gente. Primero, PreToolUse es la excepción al patrón de decision de nivel superior: históricamente usaba decision/reason de nivel superior, pero están obsoletos para este evento ("approve"/"block" se corresponden con "allow"/"deny"); usa hookSpecificOutput.permissionDecision.5 Segundo, cuando varios ganchos de PreToolUse no coinciden, la precedencia es deny > defer > ask > allow.5
Configuración: settings.json, matchers, alcance
La configuración de ganchos anida 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 gancho para ejecutar.6
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "/path/to/lint-check.sh" }
]
}
]
}
}
Dónde lo coloques determina el alcance: ~/.claude/settings.json se aplica a todos tus proyectos, .claude/settings.json está acotado al proyecto y se puede commitear, .claude/settings.local.json está acotado al proyecto y queda en gitignore, y rige la precedencia estándar de la configuración: la política gestionada por encima de la local, esta por encima de la del proyecto y esta por encima de la del usuario.9 Los ganchos también pueden venir en plugins (hooks/hooks.json) y en el frontmatter de skills o agentes, y los administradores de empresa pueden imponer ganchos gestionados que los usuarios no pueden anular.6
Los matchers se evalúan por sus caracteres: "*", "" o un matcher omitido coinciden con todo; un valor que solo contiene letras, dígitos, _, -, espacios, comas y | es una cadena o lista exacta (Bash, Edit|Write); cualquier otra cosa se convierte en una regex de JavaScript sin anclar, de modo que Edit.* coincide tanto con Edit como con NotebookEdit; ancla con ^Edit$ cuando te refieras exactamente a una herramienta. Los matchers distinguen mayúsculas y minúsculas, y cada evento hace coincidir su propio campo: el nombre de la herramienta para los eventos de herramienta, source para SessionStart, el tipo de agente para SubagentStart, el tipo de notificación para Notification.6 Para un filtrado más preciso en los eventos de herramienta, el campo if de cada handler acepta una regla de permiso como "Bash(git *)"; pero funciona con el mejor esfuerzo posible (falla en abierto ante comandos que no puede analizar), así que usa reglas de permiso, no if, para garantías firmes.6
Los handlers vienen en cinco tipos: command (shell), http (endpoint POST), mcp_tool, prompt (evaluación del modelo en un solo turno) y agent (un subagente con acceso a Read/Grep/Glob; experimental). Tiempos de espera predeterminados: 600 segundos para command/http/mcp_tool (reducidos a 30 para UserPromptSubmit y a 10 para MessageDisplay), 30 para prompt, 60 para agent; anúlalos por gancho con timeout.6 Todos los ganchos que coinciden se ejecutan en paralelo, con los handlers idénticos deduplicados, y $CLAUDE_PROJECT_DIR apunta los scripts a la raíz de tu proyecto.
Verifícalo con /hooks: un explorador de solo lectura que muestra cada evento, sus ganchos configurados y de qué archivo de configuración proviene cada uno. Para cambiar algo, edita el JSON (o pídele a Claude que lo haga). Para desactivarlo todo temporalmente, establece "disableAllHooks": true.6
Cinco patrones
Genéricos y mínimos. El tutorial de ganchos construye versiones de producción más completas de varios de ellos, y Ganchos para el desarrollo en Apple los aplica a la cadena de herramientas de iOS.
1. Formateo automático después de las ediciones (PostToolUse)
Directo de la guía oficial: cada archivo que Claude toca se formatea, 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 lo exija tu stack.
2. Bloquear comandos peligrosos (PreToolUse, código de salida 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 de salida 2 bloquea la llamada y 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 con margen para crecer hacia "ask" (escalar al humano) o updatedInput (reescribir el comando).5
3. Inyectar contexto al iniciar la sesión (SessionStart)
El stdout plano de un gancho de 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
Usa esto para el estado dinámico. Las convenciones estáticas van en CLAUDE.md, que la propia documentación recomienda para el contexto que no requiere un script.1
4. Una puerta de finalización en 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 de forma estricta un gancho de Stop a 8 bloqueos consecutivos, y una puerta que nunca comprueba si ya desencadenó una continuación los agotará de golpe.7 Para una guía más suave, devuelve hookSpecificOutput.additionalContext en lugar de decision: "block": la misma continuación, pero como retroalimentación etiquetada en vez de un error de gancho. Y para condiciones puntuales, el comando integrado /goal es un gancho de Stop basado en prompt, acotado a la sesión y con cero configuración.1
5. El despachador: un único punto de entrada, muchos ganchos pequeños
Registrar diez ganchos significa diez entradas en settings.json que se desincronizan entre máquinas y proyectos. La alternativa: registra un único despachador por evento y enruta 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 protección ahora es un chmod +x sobre un archivo nuevo en .claude/hooks/PreToolUse/: settings.json nunca cambia, cada script se mantiene lo bastante pequeño para probarlo de forma aislada, y el primer código de salida 2 se propaga. Una advertencia: el despachador serializa lo que Claude Code ejecutaría en paralelo, y encaja mejor con ganchos basados en códigos de salida; un gancho que emite JSON debería quedar independiente, ya que stdout debe contener exactamente un objeto JSON.5
Gancho vs. CLAUDE.md vs. skill vs. memoria
Cuatro mecanismos, cuatro funciones:
| Mecanismo | Función | Regla para elegir |
|---|---|---|
| Gancho | Aplicación | Si saltárselo debe ser imposible —formateo, seguridad, puertas—, es un gancho |
| CLAUDE.md | Guía | 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 corresponde, 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 va en ambas direcciones. Codificar convenciones como ganchos te deja scripts frágiles que imponen cosas que una frase de guía resuelve sin problema. Codificar política como prosa de CLAUDE.md te deja un agente que hace force-push a main justo el día en que importa. La prueba: ¿cuál es el costo cuando el modelo ignora esto una vez? Molestia → CLAUDE.md. Incidente → gancho.
Lo que los ganchos no pueden hacer
Límites honestos, todos de la documentación oficial:7
- Los ganchos no pueden llamar herramientas ni comandos de barra. Los ganchos de comando hablan stdout, stderr y códigos de salida, nada más. El contexto devuelto mediante
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 interrupciones del usuario (los errores de la API disparanStopFailureen su lugar). La lógica de la puerta debe tolerar cierres a mitad de tarea.PermissionRequestno se dispara en modo headless (-p). UsaPreToolUsepara las decisiones de permiso automatizadas.PreToolUseno ve los archivos referenciados con@. Los archivos incorporados con@en tu prompt no implican ninguna llamada a herramienta; usa reglas de denegación deReadpara proteger rutas por esa vía.1- El
updatedInputen paralelo no es determinista. Cuando varios ganchos de PreToolUse reescriben los argumentos de la misma herramienta, gana el último en terminar. Deja que un solo gancho se encargue de cada reescritura. - Los tiempos de espera cancelan el gancho. 600 segundos por defecto para los ganchos de comando (30 para
UserPromptSubmit, 10 paraMessageDisplay); una puerta lenta que agota el tiempo es una puerta que no se ejecutó. - La salida está limitada a 10.000 caracteres; el excedente se escribe en un archivo y se reemplaza por una vista previa.
- Los ganchos se ejecutan con todos tus permisos de usuario. La propia advertencia de la referencia: pueden «modificar, eliminar o acceder a cualquier archivo al que tu cuenta de usuario pueda acceder. Revisa y prueba todos los comandos de gancho antes de añadirlos a tu configuración».8 Entrecomilla tus variables, usa rutas absolutas, evita los archivos sensibles.
- Un gancho roto degrada todas las sesiones hasta que se arregle. Depúralo con la vista de la transcripción (
Ctrl+O),claude --debug-file /tmp/claude.logo/debuga mitad de sesión; un tropiezo clásico es un perfil de shell que imprime algo al arrancar y corrompe la salida JSON de tu gancho.7
Preguntas frecuentes
¿Qué son los ganchos de Claude Code?
Los ganchos son comandos definidos por el usuario —scripts de shell, endpoints HTTP, herramientas de MCP o prompts para el modelo— que Claude Code ejecuta automáticamente en puntos específicos del ciclo de vida.3 Reciben JSON del evento por stdin y responden con códigos de salida o 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, sin importar el comportamiento del modelo.
¿Cuál es la diferencia entre los ganchos de PreToolUse y los permisos?
Las reglas de permiso son declarativas: patrones estáticos de permitir/denegar/preguntar que Claude Code evalúa por sí mismo. Los ganchos de PreToolUse son programables: tu código inspecciona toda la entrada de la herramienta y decide. Los ganchos se disparan antes de las comprobaciones del modo de permisos, de modo que el "deny" de un gancho se mantiene incluso en modo bypassPermissions; pero el "allow" de un gancho no puede anular una regla de denegación de la configuración.4 Usa reglas de permiso para todo lo que un patrón pueda expresar; recurre a un gancho cuando la decisión requiera lógica, estado externo o reescritura de la entrada.
¿Funcionan los ganchos en modo headless (-p)?
Sí, con una excepción documentada: los ganchos de PermissionRequest no se disparan en modo no interactivo, así que las decisiones de permiso automatizadas van en PreToolUse.7 El modo headless también desbloquea una opción que las sesiones interactivas ignoran: permissionDecision: "defer", que pausa una llamada a herramienta para que un proceso envolvente (una app del Agent SDK, una UI personalizada) pueda recopilar datos y reanudar la sesión más tarde.5
¿Por qué mi gancho se ejecuta pero no bloquea nada?
Casi siempre es una violación del contrato. El código de salida 1 no bloquea; solo lo hace el 2, y solo en los eventos que admiten bloqueo.2 Las decisiones en JSON solo se analizan con el código de salida 0; un script que imprime {"decision": "block"} y luego sale con 2 ve descartado su JSON. Y los matchers distinguen mayúsculas y minúsculas: bash nunca coincide con Bash. Confirma el registro con /hooks; luego pruébalo enviando por tubería un JSON de ejemplo al script y revisando echo $?.7
Fuentes
Verificado contra la documentación oficial el 1 de julio de 2026. La API de ganchos ha cambiado de forma significativa a lo largo de las versiones v2.1.x de Claude Code (nuevos eventos, nuevos campos, semántica de matchers), así que trata los detalles sensibles a la versión como «al día de esta fecha».
Relacionado en este sitio: la sección de ganchos de la guía de Claude Code para la visión del sistema completo, incluidos los ganchos de prompt y de agente; el tutorial de ganchos para cinco construcciones de producción con configuraciones completas; Ganchos para el desarrollo en Apple para los patrones aplicados de iOS; y el quickstart si aún 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, the /hooks menu.” 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 ↩