Automatizar acciones con hooks
Ejecuta comandos de shell automáticamente cuando Claude Code edita archivos, finaliza tareas o necesita entrada. Formatea código, envía notificaciones, valida comandos y aplica reglas del proyecto.
Los hooks son comandos de shell definidos por el usuario. Claude Code los ejecuta en puntos específicos de su ciclo de vida, lo que le proporciona control determinista: ciertas acciones siempre ocurren en lugar de depender de que el LLM elija ejecutarlas. Utilice hooks para aplicar reglas del proyecto, automatizar tareas repetitivas e integrar Claude Code con sus herramientas existentes.
Para decisiones que requieren criterio en lugar de reglas deterministas, también puedes usar hooks basados en prompts o hooks basados en agentes que utilizan un modelo Claude para evaluar condiciones.
Para otras formas de extender Claude Code, consulte skills para dar a Claude instrucciones adicionales y comandos ejecutables, subagents para ejecutar tareas en contextos aislados, y plugins para empaquetar extensiones para compartir entre proyectos.
[!TIP]
Esta guía cubre casos de uso comunes y cómo comenzar. Para esquemas de eventos completos, formatos de entrada/salida JSON y características avanzadas como hooks asincronos y hooks de herramientas MCP, consulta la referencia de Hooks.
Configura tu primer hook
Para crear un hook, añade un bloque hooks a un archivo de configuración. Este tutorial crea un hook de notificación de escritorio, para que recibas una alerta cada vez que Claude esté esperando tu entrada en lugar de ver la terminal.
1. Añade el hook a tu configuración
Abre ~/.claude/settings.json y añade un hook Notification. Si el archivo no existe, créalo. El ejemplo a continuación usa osascript para macOS; consulta Recibe notificaciones cuando Claude necesita entrada para comandos de Linux y Windows.
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}
Si tu archivo de configuración ya tiene una clave hooks, añade Notification como hermano de las claves de evento existentes en lugar de reemplazar el objeto completo. Cada nombre de evento es una clave dentro del único objeto hooks:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }]
}
],
"Notification": [
{
"matcher": "",
"hooks": [{ "type": "command", "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'" }]
}
]
}
}
También puedes pedirle a Claude que escriba el hook por ti describiendo lo que quieres en la CLI.
2. Verifica la configuración
Escribe /hooks en el prompt de Claude Code para abrir el navegador de hooks. Tu nuevo hook aparece en la lista bajo Notification.
3. Prueba el hook
Presiona Esc para volver a la CLI. Presiona Shift+Tab hasta que la barra de estado muestre ⏸ manual mode on, pídele a Claude que haga algo que requiera permiso, luego cambia de la terminal. Deberías recibir una notificación de escritorio.
Qué puedes automatizar
Los hooks te permiten ejecutar código en puntos clave del ciclo de vida de Claude Code: formatear archivos después de las ediciones, bloquear comandos antes de que se ejecuten, enviar notificaciones cuando Claude necesita tu intervención, inyectar contexto al inicio de la sesión y más. Para ver la lista completa de eventos de hooks, consulta la referencia de Hooks.
Cada ejemplo incluye un bloque de configuración listo para usar que agregas a un archivo de configuración.
Para ver un ejemplo en producción de hooks que ejecutan una revisión con un modelo separado y devuelven los hallazgos a la sesión, consulta cómo se integra el plugin security-guidance con Claude Code.
Recibe una notificación cuando Claude necesite tu intervención
Recibe una notificación de escritorio cada vez que Claude termine de trabajar y necesite tu intervención, para que puedas cambiar a otras tareas sin revisar la terminal.
Este hook usa el evento Notification, que Claude Code dispara cuando Claude está esperando una entrada o un permiso. Consulta cuándo se dispara cada tipo de notificación para conocer el momento exacto.
Cada pestaña a continuación usa el comando de notificación nativo de la plataforma. Agrega esto a ~/.claude/settings.json:
[tab] macOS
{ "hooks": { "Notification": [ { "matcher": "", "hooks": [ { "type": "command", "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'" } ] } ] } }[accordion] Si no aparece ninguna notificación
osascriptenvía las notificaciones a través de la app integrada Script Editor. Si Script Editor no tiene permiso para mostrar notificaciones, el comando falla en silencio y macOS no te pedirá que lo concedas.Ejecuta esto en Terminal una vez para que Script Editor aparezca en tu configuración de notificaciones:
osascript -e 'display notification "test"'Todavía no aparecerá nada. Abre System Settings > Notifications, busca Script Editor en la lista y activa Allow Notifications. Ejecuta el comando de nuevo para confirmar que aparece la notificación de prueba.
[tab] Linux
{ "hooks": { "Notification": [ { "matcher": "", "hooks": [ { "type": "command", "command": "notify-send 'Claude Code' 'Claude Code needs your attention'" } ] } ] } }[accordion] Si no aparece ninguna notificación
notify-sendnecesita un daemon de notificaciones de escritorio, que los servidores sin interfaz gráfica, las sesiones SSH y la mayoría de los contenedores no tienen. Primero prueba el comando directamente:notify-send 'Claude Code' 'test'Si no se encuentra el comando, instala el paquete
libnotify-binen Debian y Ubuntu, o el equivalente de tu distribución.
[tab] Windows (PowerShell)
{ "hooks": { "Notification": [ { "matcher": "", "hooks": [ { "type": "command", "command": "powershell.exe -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code needs your attention', 'Claude Code')\"" } ] } ] } }[accordion] Si no aparece ningún cuadro de diálogo
Este comando abre un cuadro de diálogo en lugar de una notificación en la esquina de tu pantalla, por lo que el cuadro de diálogo puede abrirse detrás de la ventana de tu terminal. Primero prueba el comando directamente en PowerShell.
Si ejecutas Claude Code dentro de WSL,
powershell.exedebe estar disponible en tuPATHmediante la interoperabilidad de Windows.
El matcher vacío se dispara con todos los tipos de notificación. Para que se dispare solo con eventos específicos, establécelo en uno de estos valores:
| Matcher | Se dispara cuando |
|---|---|
permission_prompt |
Claude necesita que apruebes el uso de una herramienta o la solicitud de red de un comando en sandbox, y la solicitud de permiso lleva unos seis segundos esperando |
idle_prompt |
Claude terminó de responder hace unos 60 segundos y no has escrito nada desde entonces |
auth_success |
Se completa la autenticación |
elicitation_dialog |
Un servidor MCP abre un formulario de elicitación y no has escrito nada durante unos seis segundos |
elicitation_url_dialog |
Un servidor MCP te pide que abras una URL en el navegador y no has escrito nada durante unos seis segundos |
elicitation_complete |
Un servidor MCP informa que una elicitación en modo URL está completa |
elicitation_response |
Se envía de vuelta al servidor una respuesta de elicitación MCP |
agent_needs_input |
Una sesión en segundo plano empieza a esperar tu intervención mientras la vista de agentes está abierta. También se dispara cuando una sesión de terminal te muestra la pregunta de configuración de terminal de un compañero del equipo de agentes o el aviso del modo automático sobre cargos por solicitudes del clasificador y no has escrito nada durante unos seis segundos |
agent_completed |
Una sesión en segundo plano termina o falla. Solo se dispara mientras la vista de agentes está abierta |
quota_auto_resume_fired |
Claude Code continúa tu tarea después de que un límite de uso de claude.ai la pausara: al restablecerse, o antes cuando algo que haces en Claude Code durante la espera, como agregar créditos de uso, mejorar tu plan o cambiar de modelo, vuelve a hacer disponible el uso, con la excepción del ajuste de modelo |
quota_auto_resume_stale |
Un límite de uso de claude.ai se restableció mientras tu computadora estuvo en reposo durante más de unos 30 minutos. Claude Code espera a que presiones Enter en lugar de continuar. Tras un reposo más corto, continúa y dispara quota_auto_resume_fired en su lugar |
quota_auto_resume_disabled |
Claude Code termina su espera de un límite de uso de claude.ai sin continuar tu tarea: autoContinueAtUsageLimit se desactivó o el restablecimiento se movió a más de 24 horas durante una espera que Claude Code inició por su cuenta, la tarea continuada siguió alcanzando el límite, o la continuación se bloqueó antes de llegar al modelo. No se dispara cuando presionas Esc o Ctrl+C, ni cuando eliges Don't continue automatically |
plugin_notification |
Un mod genera una notificación con $.ui.notify. El title del hook es el nombre del mod, a menos que el mod haya establecido uno |
Claude Code mide el tiempo de permission_prompt de forma diferente en una terminal y en Claude Desktop, la extensión de VS Code y otros hosts que responden a las solicitudes de permiso a través del Agent SDK. Consulta cuándo se dispara cada tipo de notificación para conocer ambos tiempos.
Los matchers quota_auto_resume_fired, quota_auto_resume_stale y quota_auto_resume_disabled requieren Claude Code v2.1.234 o posterior.
En sesiones de terminal, permission_prompt para la solicitud de red de un comando en sandbox requiere Claude Code v2.1.246 o posterior.
agent_needs_input para la pregunta de configuración de terminal de un compañero requiere Claude Code v2.1.248 o posterior.
Escribe /hooks en el prompt de Claude Code y confirma que el hook aparece en Notification.
Formatea el código automáticamente después de las ediciones
Ejecuta automáticamente Prettier en cada archivo que Claude edite, para que el formato se mantenga consistente sin intervención manual.
Este hook usa el evento PostToolUse con un matcher Edit|Write, por lo que solo se ejecuta después de las herramientas de edición de archivos. El comando extrae la ruta del archivo editado con jq y se la pasa a Prettier. Agrega esto a .claude/settings.json en la raíz de tu proyecto:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
Para probar el hook, pídele a Claude que agregue una línea con cadenas entre comillas simples a un archivo JavaScript y luego abre el archivo: con la configuración predeterminada de Prettier, el hook las reescribe con comillas dobles.
Cuando el hook se ejecuta correctamente, Claude Code no muestra nada en la conversación. Para confirmar que el hook se ejecutó, verifica que el archivo editado se haya reformateado o consulta Comprueba qué hizo un hook.
Para reformatear un archivo específico sin importar cómo cambie, incluso cuando un comando Bash lo reescribe, usa en su lugar un hook FileChanged.
[!NOTE]
Los ejemplos de Bash de esta página usan
jqpara analizar JSON. Instálalo conbrew install jqen macOS,apt-get install jqen Debian y Ubuntu, o consulta las descargas dejq.
Bloquea ediciones en archivos protegidos
Evita que Claude modifique archivos sensibles como .env, package-lock.json o cualquier cosa dentro de .git/. Claude recibe comentarios que explican por qué se bloqueó la edición, para que pueda ajustar su enfoque.
Este ejemplo usa un archivo de script separado al que llama el hook. El script compara la ruta del archivo de destino con una lista de patrones protegidos y termina con el código 2 para bloquear la edición.
1. Crea el script del hook
Guarda esto en .claude/hooks/protect-files.sh:
#!/bin/bash
# protect-files.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Normalize Windows backslash separators so the patterns below match
FILE_PATH="${FILE_PATH//\\//}"
PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
exit 2
fi
done
exit 0
2. Haz que el script sea ejecutable en macOS y Linux
Los scripts de hooks deben ser ejecutables para que Claude Code pueda ejecutarlos:
chmod +x .claude/hooks/protect-files.sh
3. Registra el hook
Agrega un hook PreToolUse a .claude/settings.json que ejecute el script antes de cualquier llamada a las herramientas Edit o Write:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}
4. Prueba el hook
Pídele a Claude que agregue un comentario a tu archivo .env. Claude Code bloquea la edición antes de que se ejecute y le pasa a Claude el mensaje Blocked: del script como retroalimentación.
Vuelve a inyectar contexto después de la compactación
Cuando la ventana de contexto de Claude se llena, la compactación resume la conversación para liberar espacio. Esto puede hacer que se pierdan detalles importantes. Usa un hook SessionStart con un matcher compact para volver a inyectar contexto crítico después de cada compactación.
Claude Code agrega al contexto de Claude el texto plano que tu comando escribe en stdout. Este ejemplo le recuerda a Claude las convenciones del proyecto y el trabajo reciente. Agrega esto a .claude/settings.json en la raíz de tu proyecto:
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo 'Reminder: use Bun, not npm. Run bun test before committing. Current sprint: auth refactor.'"
}
]
}
]
}
}
Puedes reemplazar el echo con cualquier comando que produzca una salida dinámica, como git log --oneline -5 para mostrar los commits recientes. Para inyectar contexto al inicio de cada sesión, considera usar CLAUDE.md en su lugar. Para las variables de entorno, consulta CLAUDE_ENV_FILE en la referencia.
Audita los cambios de configuración
Haz un seguimiento de cuándo cambian los archivos de configuración o de skills durante una sesión. El evento ConfigChange se dispara cuando un proceso externo o un editor modifica un archivo de configuración, para que puedas registrar los cambios por motivos de cumplimiento o bloquear modificaciones no autorizadas.
Este ejemplo añade cada cambio a un registro de auditoría. Agrega esto a ~/.claude/settings.json:
{
"hooks": {
"ConfigChange": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "jq -c '{timestamp: now | todate, source: .source, file: .file_path}' >> ~/claude-config-audit.log"
}
]
}
]
}
}
El matcher filtra por tipo de configuración: user_settings, project_settings, local_settings, policy_settings o skills. Para impedir que un cambio surta efecto, termina con el código 2 o devuelve {"decision": "block"}. Consulta la referencia de ConfigChange para ver el esquema de entrada completo.
Para confirmar que el hook registra los cambios, edita un archivo de configuración en otro editor mientras se ejecuta una sesión y luego abre ~/claude-config-audit.log: el hook añade una línea JSON por cambio con la marca de tiempo, el origen y la ruta del archivo.
Recarga el entorno cuando cambien el directorio o los archivos
Algunos proyectos establecen distintas variables de entorno según el directorio en el que te encuentres. Herramientas como direnv hacen esto automáticamente en tu shell, pero la herramienta Bash de Claude no detecta esos cambios por sí sola.
Combinar un hook SessionStart con un hook CwdChanged resuelve esto. SessionStart carga las variables del directorio en el que inicias, y CwdChanged las recarga cada vez que Claude cambia de directorio. Ambos escriben en CLAUDE_ENV_FILE, que Claude Code ejecuta como preámbulo de script antes de cada comando Bash. Agrega esto a ~/.claude/settings.json:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
}
]
}
],
"CwdChanged": [
{
"hooks": [
{
"type": "command",
"command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
}
]
}
]
}
}
Ejecuta direnv allow una vez en cada directorio que tenga un .envrc para que direnv tenga permiso de cargarlo. Si usas devbox o nix en lugar de direnv, el mismo patrón funciona con devbox shellenv o devbox global shellenv en lugar de direnv export bash.
Para reaccionar a archivos específicos en lugar de a cada cambio de directorio, usa FileChanged con un matcher que enumere los nombres de archivo que quieres vigilar, separados por |. Al construir la lista de vigilancia, Claude Code divide este valor en nombres de archivo literales en lugar de evaluarlo como una expresión regular. Consulta FileChanged para ver cómo el mismo valor también filtra qué grupos de hooks se ejecutan cuando cambia un archivo. Este ejemplo vigila .envrc y .env en el directorio de trabajo:
{
"hooks": {
"FileChanged": [
{
"matcher": ".envrc|.env",
"hooks": [
{
"type": "command",
"command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
}
]
}
]
}
}
Consulta las entradas de referencia de CwdChanged y FileChanged para ver los esquemas de entrada, la salida watchPaths y los detalles de CLAUDE_ENV_FILE.
Aprueba automáticamente solicitudes de permiso específicas
Omite el cuadro de diálogo de aprobación para las llamadas a herramientas que siempre permites. Este ejemplo aprueba automáticamente ExitPlanMode, la herramienta que Claude llama cuando termina de presentar un plan y pide continuar, para que no se te pida confirmación cada vez que un plan esté listo.
A diferencia de los ejemplos anteriores con códigos de salida, la aprobación automática requiere que tu hook escriba una decisión JSON en stdout. Claude Code ejecuta los hooks PermissionRequest cuando está a punto de pedirte permiso, y si tu hook devuelve "behavior": "allow", Claude Code responde a la solicitud en tu nombre.
El matcher limita el hook solo a ExitPlanMode, por lo que ninguna otra solicitud de permiso se ve afectada. Agrega esto a ~/.claude/settings.json:
{
"hooks": {
"PermissionRequest": [
{
"matcher": "ExitPlanMode",
"hooks": [
{
"type": "command",
"command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'"
}
]
}
]
}
}
Cuando el hook aprueba, Claude Code sale del modo plan y restaura el modo de permisos que estaba activo antes de que entraras en el modo plan. La transcripción muestra "Allowed by PermissionRequest hook" donde habría aparecido el cuadro de diálogo. La ruta del hook siempre conserva la conversación actual: no puede borrar el contexto e iniciar una nueva sesión de implementación como sí puede hacerlo el cuadro de diálogo.
Para establecer un modo de permisos específico, la salida de tu hook puede incluir un arreglo updatedPermissions con una entrada setMode. El valor de mode es cualquier modo de permisos, como default, acceptEdits o bypassPermissions, y destination: "session" lo aplica solo a la sesión actual.
[!NOTE]
bypassPermissionssolo se aplica si iniciaste la sesión con el modo bypass ya disponible:--dangerously-skip-permissions,--permission-mode bypassPermissions,--allow-dangerously-skip-permissionsopermissions.defaultMode: "bypassPermissions"en la configuración de usuario, de--settingso administrada. No se aplica si el modo bypass está desactivado mediantepermissions.disableBypassPermissionsMode, o si iniciaste la sesión en modo restringido.Claude Code nunca lo guarda como
defaultMode.
Para cambiar la sesión a acceptEdits, tu hook escribe este JSON en stdout:
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedPermissions": [
{ "type": "setMode", "mode": "acceptEdits", "destination": "session" }
]
}
}
}
Mantén el matcher lo más específico posible. Usar .* o dejar el matcher vacío aprobaría automáticamente todas las solicitudes de permiso de herramientas, incluidas las escrituras de archivos y los comandos de shell. Consulta la referencia de PermissionRequest para ver el conjunto completo de campos de decisión.
Cómo funcionan los hooks
Claude Code dispara eventos de hook en puntos específicos de su ciclo de vida. Cuando se dispara un evento, Claude Code ejecuta los hooks coincidentes en paralelo; consulta Campos del manejador de hooks para saber cómo se tratan los manejadores duplicados. La tabla a continuación muestra cada evento y cuándo se dispara:
| Evento | Cuándo se dispara |
|---|---|
SessionStart |
Cuando una sesión comienza o se reanuda |
Setup |
Cuando inicia Claude Code con --init-only, o con --init o --maintenance en modo -p. Para preparación única en CI o scripts |
UserPromptSubmit |
Cuando se envía un prompt, antes de que Claude lo procese. También se dispara en turnos que Claude Code inicia por su cuenta |
UserPromptExpansion |
Cuando un comando escrito por el usuario se expande en un prompt, antes de que llegue a Claude. Puede bloquear la expansión |
PreToolUse |
Antes de que se ejecute una llamada a herramienta. Puede bloquearlo |
PermissionRequest |
Cuando una llamada a herramienta necesita una decisión de permiso |
PermissionDenied |
Cuando el modo automático deniega una llamada a herramienta, incluidas las denegaciones sin un veredicto del clasificador. Use JSON hookSpecificOutput.retry: true para indicar al modelo que puede reintentar la llamada a herramienta denegada. Claude Code ignora retry cuando el clasificador no produjo veredicto |
PostToolUse |
Después de que una llamada a herramienta se ejecuta correctamente |
PostToolUseFailure |
Después de que una llamada a herramienta falla |
PostToolBatch |
Después de que se resuelve un lote completo de llamadas a herramientas paralelas, antes de la siguiente llamada al modelo |
Notification |
Cuando Claude Code envía una notificación |
MessageDisplay |
Mientras se muestra el texto del mensaje del asistente |
SubagentStart |
Cuando se genera un subagente |
SubagentStop |
Cuando un subagente finaliza |
TaskCreated |
Cuando se está creando una tarea a través de TaskCreate |
TaskCompleted |
Cuando se marca una tarea como completada |
Stop |
Cuando Claude termina de responder |
StopFailure |
Cuando el turno termina debido a un error de API |
TeammateIdle |
Cuando un compañero de equipo de agentes está a punto de quedarse inactivo |
InstructionsLoaded |
Cuando se carga un archivo CLAUDE.md o .claude/rules/*.md en el contexto. Se dispara al inicio de la sesión y cuando los archivos se cargan de forma diferida durante una sesión |
ConfigChange |
Cuando un archivo de configuración cambia durante una sesión |
CwdChanged |
Cuando el directorio de trabajo cambia, por ejemplo cuando Claude ejecuta un comando cd. Útil para la gestión reactiva del entorno con herramientas como direnv |
DirectoryAdded |
Cuando se agrega un directorio de trabajo a mitad de sesión a través de /add-dir o la solicitud de control register_repo_root del SDK |
FileChanged |
Cuando un archivo observado cambia en el disco. El campo matcher especifica qué nombres de archivo observar |
WorktreeCreate |
Cuando se está creando un worktree a través de --worktree, isolation: "worktree", o para una sesión en segundo plano. Reemplaza el comportamiento predeterminado de git |
WorktreeRemove |
Cuando se está eliminando un worktree que creó un hook WorktreeCreate |
PreCompact |
Antes de la compactación de contexto |
PostCompact |
Después de que se completa la compactación de contexto |
PreModelSwitch |
Antes de que Claude Code aplique un cambio de modelo que usted o un cliente solicitó. Puede bloquear el cambio |
PostModelSwitch |
Después de que cambia el modelo de la sesión, incluidos los cambios que Claude Code realiza por su cuenta, como restaurar el modelo cuando reanuda una sesión |
Elicitation |
Cuando un servidor MCP solicita entrada del usuario durante una llamada a herramienta |
ElicitationResult |
Después de que un usuario responde a una solicitud de MCP, antes de que la respuesta se envíe de vuelta al servidor |
SessionEnd |
Cuando una sesión termina |
Cada hook tiene un type que determina cómo se ejecuta. La mayoría de los hooks usan "type": "command", que ejecuta un comando de shell. Hay otros cuatro tipos disponibles:
"type": "http": POST de datos de evento a una URL. Consulta HTTP hooks."type": "mcp_tool": llamar a una herramienta en un servidor MCP configurado. Consulta MCP tool hooks."type": "prompt": evaluación LLM de un solo turno. Consulta Prompt-based hooks."type": "agent": verificación multi-turno con acceso a herramientas. Los hooks de agente son experimentales y pueden cambiar. Consulta Agent-based hooks.
Combina resultados de múltiples hooks
Cuando múltiples hooks coinciden con el mismo evento, el comando de cada hook se ejecuta hasta completarse antes de que Claude Code fusione los resultados. Un hook que devuelve deny no detiene la ejecución de hooks hermanos. No confíes en que el deny de un hook suprima efectos secundarios en otro hook.
Después de que todos los hooks coincidentes terminen, Claude Code combina sus salidas. Para decisiones de permiso PreToolUse, la respuesta más restrictiva gana, en el orden deny, defer, ask, allow. El texto de additionalContext se mantiene de cada hook y se pasa a Claude junto.
El ejemplo a continuación registra dos hooks PreToolUse en Bash. El primero añade cada comando a un archivo de registro y sale con 0. El segundo ejecuta un script que sale con 2 para negar cuando el comando contiene rm -rf:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r .tool_input.command >> ~/.claude/bash.log"
},
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm-rf.sh"
}
]
}
]
}
}
Cuando Claude intenta ejecutar rm -rf /tmp/build, ambos hooks se ejecutan en paralelo. El hook de registro escribe el comando en ~/.claude/bash.log y sale con 0, lo que no reporta ninguna decisión. El hook de protección sale con 2, lo que niega la llamada a herramienta. El deny gana, por lo que Claude Code bloquea el comando y muestra a Claude el stderr del guardrail. La entrada de registro se sigue escribiendo porque el hook de registro ya se ejecutó.
Lee entrada y devuelve salida
Los hooks se comunican con Claude Code a través de stdin, stdout, stderr y códigos de salida. Cuando se activa un evento, Claude Code pasa datos específicos del evento como JSON a stdin de tu script. Tu script lee esos datos, hace su trabajo, y le dice a Claude Code qué hacer a continuación a través del código de salida.
Entrada del hook
Cada evento incluye campos comunes como session_id, un ID único para la sesión, y cwd, el directorio de trabajo cuando se disparó el evento, pero cada tipo de evento añade datos diferentes. Cuando Claude ejecuta un comando Bash, un hook PreToolUse recibe estos campos en stdin:
hook_event_name: el evento que activó el hooktool_name: la herramienta que Claude está a punto de usartool_input: los argumentos que Claude pasó a la herramienta. Para Bash, su campocommandcontiene el comando de shell.
Por ejemplo, la entrada del hook para un comando npm test se ve así:
{
"session_id": "abc123",
"cwd": "/Users/sarah/myproject",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}
Tu script puede analizar ese JSON y actuar sobre cualquiera de esos campos. Los hooks UserPromptSubmit obtienen el texto prompt en su lugar, los hooks SessionStart obtienen una source de startup, resume, clear, compact, o fork, y así sucesivamente. Consulta Campos de entrada comunes en la referencia para campos compartidos, y la sección de cada evento para esquemas específicos del evento.
Salida del hook
Tu script le dice a Claude Code qué hacer a continuación escribiendo en stdout o stderr y saliendo con un código específico. El siguiente hook PreToolUse bloquea un comando:
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q "drop table"; then
echo "Blocked: dropping tables is not allowed" >&2 # stderr se convierte en retroalimentación de Claude
exit 2 # exit 2 = bloquea la acción
fi
exit 0 # exit 0 = sin decisión; se aplica el flujo de permisos normal
Con un código de salida distinto de 0 y 2 y sin una salida JSON válida, la acción procede en la mayoría de los eventos, a menos que el hook sea uno cuyo fallo bloquea la acción. Un hook con onFailure establecido en "block" es un ejemplo. Sal con 0 para reportar que no hay objeción, o sal con 2 para bloquear la acción:
- Exit 0: si sales con 0 desde un hook
PreToolUse, la llamada a herramienta sigue pasando por el flujo de permisos normal, porque exit 0 no la aprueba. Para hooksUserPromptSubmit,UserPromptExpansion,SessionStart, yPostModelSwitch, Claude Code añade stdout que trata como texto plano al contexto de Claude. - Exit 2: escribe una razón en stderr. Según el evento, Claude la recibe como retroalimentación para poder ajustarse, el usuario la ve, o no aparece ningún mensaje, como ocurre con
ConfigChangeyElicitation. Algunos eventos no pueden ser bloqueados: paraSessionStarty otros, exit 2 muestra stderr al usuario y la ejecución continúa. Consulta comportamiento del código de salida 2 por evento para la lista completa. - Cualquier otro código de salida: para las excepciones por evento, como
WorktreeCreateque falla ante cualquier salida distinta de cero, consulta Salida del código de salida. En otros eventos, a menos que el hook sea uno cuyo fallo bloquea la acción, encuentra tu caso según lo que tu hook imprimió en stdout:- Un objeto analizado que pasa validación de esquema: Claude Code ignora el código de salida, el JSON solo decide el resultado, y el hook no se reporta como un error.
- Un objeto analizado que falla validación de esquema, o stdout que Claude Code intenta analizar como JSON pero que no es JSON válido: la acción procede como un error sin bloqueo. Revisa el aviso
<hook name> hook errorpara ver el mensaje de validación o análisis. - Stdout que Claude Code trata como texto plano, o stdout vacío: la acción procede como un error sin bloqueo. La transcripción muestra un aviso
<hook name> hook error, luego la primera línea de stderr prefijada conFailed with non-blocking status code:. Para capturar el stderr completo, habilita registro de depuración conclaude --debugo ejecutando/debugen medio de la sesión.
Salida JSON estructurada
Los códigos de salida solo te permiten bloquear o permanecer en silencio. Para más control, sal con 0 e imprime un objeto JSON a stdout en su lugar.
[!NOTE]
Usa exit 2 para bloquear con un mensaje de stderr, o exit 0 con JSON para control estructurado. Elige un enfoque por hook. Para lo que sucede cuando los mezclas, consulta Salida del código de salida.
Por ejemplo, un hook PreToolUse puede negar una llamada a herramienta y decirle a Claude por qué, o escalarla al usuario para aprobación:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Use rg instead of grep for better performance"
}
}
Con "deny", Claude Code cancela la llamada a herramienta y alimenta permissionDecisionReason de vuelta a Claude.
En PreToolUse, también puedes devolver "ask" para mostrar al usuario la solicitud de permiso normal, o "allow" para omitir la solicitud de permiso interactiva. Con "allow", las reglas de negación y de solicitud, incluidas las listas de negación administradas empresariales, aún se aplican, al igual que las solicitudes para:
-
Lecturas desde rutas de red
-
Herramientas MCP marcadas con
requiresUserInteraction -
Llamadas a herramientas que una app del Agent SDK marca como
always_ask, a menos que el modo de permisos apruebe la llamada -
Herramientas de conector que tu organización configuró como
asken sesiones donde ese ajuste llega a Claude CodePara saber qué hace
"allow"en modo plan, consulta Aprobaciones de hooks en modo plan.
Un cuarto valor, "defer", está disponible en modo no interactivo con el flag -p. Devolverlo termina el turno con la llamada a herramienta preservada, para que un envoltorio del Agent SDK pueda recopilar entrada y reanudar. Consulta Defer a tool call for later en la referencia.
Un hook PreModelSwitch devuelve el mismo campo permissionDecision: "allow" permite que un cambio de modelo proceda, y "deny" lo cancela. "ask" te hace confirmar el cambio cuando ejecutas /model en una sesión interactiva; en cualquier otro lugar, Claude Code trata "ask" como un rechazo. Consulta PreModelSwitch decision control.
Otros eventos usan patrones de decisión diferentes. Por ejemplo, los hooks PostToolUse y Stop usan un campo decision: "block" de nivel superior, mientras que PermissionRequest usa hookSpecificOutput.decision.behavior. Consulta la tabla de resumen en la referencia para un desglose completo por evento.
Para hooks UserPromptSubmit, usa hookSpecificOutput.additionalContext en su lugar para inyectar texto en el contexto de Claude. Anida additionalContext dentro de hookSpecificOutput; si lo colocas en el nivel superior del JSON, Claude Code lo ignora silenciosamente. Por ejemplo, esta salida añade el estado de rama actual a cada aviso:
{
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "Current branch: release-42. Deploy freeze until Friday."
}
}
Consulta UserPromptSubmit decision control para la forma de salida completa, incluyendo bloqueo de avisos y configuración del título de sesión.
Los hooks con type: "prompt" manejan la salida de manera diferente: consulta Prompt-based hooks.
Filtra hooks con matchers
Sin un matcher, un hook se activa en cada ocurrencia de su evento. Los matchers te permiten estrecharlo. Por ejemplo, si quieres ejecutar un formateador solo después de ediciones de archivos, no después de cada llamada a herramienta, añade un matcher a tu hook PostToolUse:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "prettier --write ..." }
]
}
]
}
}
El matcher "Edit|Write" se activa solo cuando Claude usa la herramienta Edit o Write, no cuando usa Bash, Read, u otra herramienta. Una coma separa alternativas de la misma manera, por lo que "Edit, Write" es equivalente. Consulta Matcher patterns para cómo se evalúan los nombres simples y las expresiones regulares.
[!NOTE]
Claude también puede crear o modificar archivos ejecutando comandos de shell. Si tu hook debe ver cada cambio de archivo, como para escaneo de cumplimiento o registro de auditoría, añade un hook
Stopque escanee el árbol de trabajo una vez por turno. Para cobertura por llamada en su lugar, también coincide conBash|PowerShelly haz que tu script liste archivos modificados y sin seguimiento congit status --porcelain. La sección Entrada del hook PowerShell explica por qué coincidir solo conBashno es suficiente. Para ejecutar un hook cuando un archivo específico cambia en disco, sin importar qué lo escribió, usa un hook FileChanged.
Cada tipo de evento coincide en un campo específico:
| Evento | En qué filtra el matcher | Valores de matcher de ejemplo |
|---|---|---|
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied |
nombre de herramienta | Bash, Edit|Write, mcp__.* |
SessionStart |
cómo comenzó la sesión | startup, resume, clear, compact, fork |
Setup |
qué bandera CLI activó la configuración | init, maintenance |
SessionEnd |
por qué terminó la sesión | clear, resume, logout, prompt_input_exit, other |
Notification |
tipo de notificación | permission_prompt, idle_prompt, auth_success, elicitation_dialog, elicitation_url_dialog, elicitation_complete, elicitation_response, agent_needs_input, agent_completed, quota_auto_resume_fired, quota_auto_resume_stale, quota_auto_resume_disabled, plugin_notification |
SubagentStart |
tipo de agente | general-purpose, Explore, Plan, o nombres de agentes personalizados |
PreCompact, PostCompact |
qué activó la compactación | manual, auto |
PreModelSwitch, PostModelSwitch |
nombre canónico del modelo al que la sesión cambia, como se describe en PreModelSwitch | claude-opus-5, claude-opus-4-6|claude-opus-5, .*opus.* |
SubagentStop |
tipo de agente | los mismos valores que SubagentStart |
ConfigChange |
fuente de configuración | user_settings, project_settings, local_settings, policy_settings, skills |
DirectoryAdded |
cómo se añadió el directorio | slash_command, register_repo_root |
StopFailure |
tipo de error | rate_limit, overloaded, authentication_failed, oauth_org_not_allowed, account_on_hold, billing_error, invalid_request, model_not_found, server_error, max_output_tokens, cloud_credential_error, unknown |
InstructionsLoaded |
razón de carga | session_start, nested_traversal, path_glob_match, include, compact |
Elicitation |
nombre del servidor MCP | tus nombres de servidor MCP configurados |
ElicitationResult |
nombre del servidor MCP | los mismos valores que Elicitation |
FileChanged |
nombres de archivo literales a observar (consulta FileChanged) | .envrc|.env |
UserPromptExpansion |
nombre del comando | tus nombres de skill o comando |
UserPromptSubmit, PostToolBatch, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove, CwdChanged, MessageDisplay |
sin soporte de matcher | siempre se activa en cada ocurrencia |
Las pestañas a continuación muestran algunos matchers más en diferentes tipos de eventos.
[tab] Registra cada comando Bash
Coincide solo con llamadas a herramienta
Bashy registra cada comando en un archivo. El eventoPostToolUsese activa después de que el comando se completa, por lo quetool_input.commandcontiene lo que se ejecutó. El hook recibe los datos del evento como JSON en stdin, yjq -r '.tool_input.command'extrae solo la cadena de comando, que>>añade al archivo de registro:{ "hooks": { "PostToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt" } ] } ] } }
[tab] Coincide con herramientas MCP
Las herramientas MCP usan una convención de nombres diferente a las herramientas integradas:
mcp__<server>__<tool>, donde<server>es el nombre del servidor MCP y<tool>es la herramienta que proporciona. Por ejemplo,mcp__github__search_repositoriesomcp__filesystem__read_file. Las herramientas de un servidor MCP proporcionado por plugin usan un segmento de servidor con ámbito en su lugar, comomcp__plugin_my-plugin_db__query. Usa un matcher regex para dirigirse a todas las herramientas de un servidor específico, o coincide entre servidores con un patrón comomcp__.*__write.*. Consulta Match MCP tools en la referencia para la lista completa de ejemplos.El comando a continuación extrae el nombre de la herramienta de la entrada JSON del hook con
jqy lo escribe en stderr. Escribir en stderr mantiene stdout limpio para salida JSON y envía el mensaje al registro de depuración:{ "hooks": { "PreToolUse": [ { "matcher": "mcp__github__.*", "hooks": [ { "type": "command", "command": "echo \"GitHub tool called: $(jq -r '.tool_name')\" >&2" } ] } ] } }
[tab] Limpia al final de la sesión
El evento
SessionEndsoporta matchers en la razón por la que terminó la sesión. Este hook solo se activa enclear(cuando ejecutas/clear), no en salidas normales:{ "hooks": { "SessionEnd": [ { "matcher": "clear", "hooks": [ { "type": "command", "command": "rm -f /tmp/claude-scratch-*.txt" } ] } ] } }
Filtra por nombre de herramienta y argumentos con el campo if
El campo if usa sintaxis de regla de permiso para filtrar hooks por nombre de herramienta y argumentos juntos, para que el proceso del hook solo se genere cuando la llamada a herramienta coincida. Esto va más allá de matcher, que filtra a nivel de grupo solo por nombre de herramienta.
Por ejemplo, esta configuración ejecuta un hook solo cuando Claude usa comandos git en lugar de todos los comandos Bash:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(git *)",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-git-policy.sh"
}
]
}
]
}
}
Si tu hook debe ejecutarse depende de la forma de tu patrón if y del comando Bash que Claude está invocando:
Patrón if |
Comando Bash | ¿Se ejecuta el hook? | Por qué |
|---|---|---|---|
Bash(git *) |
git push |
sí | el nombre del comando coincide |
Bash(git *) |
npm test && git push |
sí | cada subcomando se verifica; git push coincide |
Bash(git *) |
echo $(git log) |
sí | los comandos dentro de $() y backticks se verifican; git log coincide |
Bash(git *) |
echo $(date) |
no | ningún subcomando coincide con git * |
Bash(git push *) |
echo $(date) |
sí | los patrones que especifican más que el nombre del comando ejecutan el hook de todas formas en $(), backticks, o $VAR |
Cuando Claude Code no puede determinar qué comandos ejecuta la entrada Bash, ejecuta tu hook independientemente del patrón. La tabla de coincidencia Bash cubre las formas de comando que Claude Code puede y no puede estrecharse por subcomando. Porque el filtro es mejor esfuerzo, usa el sistema de permisos en lugar de un hook para aplicar un allow o deny duro.
El campo if acepta los mismos patrones que las reglas de permiso: "Bash(git *)", "Edit(*.ts)", y así sucesivamente. Para coincidir con múltiples nombres de herramienta, usa manejadores separados cada uno con su propio valor if, o coincide a nivel de matcher donde se soporta alternancia de tuberías.
if solo funciona en eventos de herramienta: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, y PermissionDenied. Añadirlo a cualquier otro evento evita que el hook se ejecute.
Configura la ubicación del hook
Dónde añadas un hook determina su ámbito:
| Ubicación | Ámbito | Compartible |
|---|---|---|
~/.claude/settings.json |
Todos tus proyectos | No, local a tu máquina |
.claude/settings.json |
Proyecto único | Sí, puede ser confirmado en el repositorio |
.claude/settings.local.json |
Proyecto único | No, gitignored cuando Claude Code guarda una configuración en él |
| Configuración de política gestionada | Organización completa | Sí, controlado por administrador |
Plugin hooks/hooks.json |
Cuando el plugin está habilitado | Sí, incluido con el plugin |
| Skill frontmatter | El resto de la sesión una vez que el skill se invoca. Consulta Hooks in skills and agents | Sí, definido en el archivo del skill |
| Subagent frontmatter | Mientras ese subagente está ejecutándose | Sí, definido en el archivo del subagente |
Ejecuta /hooks en Claude Code para examinar todos los hooks configurados agrupados por evento.
Para desactivar hooks, establece "disableAllHooks": true en tu archivo de configuración. Claude Code lee el valor que queda después de que se aplica la precedencia de configuración, por lo que el archivo de configuración de un proyecto puede anular el tuyo. Los hooks configurados en configuración gestionada aún se ejecutan a menos que disableAllHooks también esté establecido allí. Para el alcance completo de cada nivel, consulta disableAllHooks.
Si editas archivos de configuración directamente mientras Claude Code está ejecutándose, el observador de archivos normalmente recoge cambios de hooks automáticamente.
Hooks basados en prompts
Para decisiones que requieren criterio en lugar de reglas deterministas, usa hooks type: "prompt". En lugar de ejecutar un comando de shell, Claude Code envía tu prompt y los datos de entrada del hook a un modelo Claude para tomar la decisión. Puedes especificar un modelo diferente con el campo model si necesitas más capacidad.
El único trabajo del modelo es devolver su decisión como JSON:
"ok": true: la acción procede"ok": false: lo que sucede depende del evento:StopySubagentStop: lareasonse alimenta de vuelta a Claude para que siga trabajando, a menos que la respuesta también establezca"impossible": truepara marcar la condición como una que nunca puede ser satisfecha, en cuyo caso Claude Code permite la parada y el turno terminaPreToolUse: la llamada a herramienta se deniega; por defecto el turno termina y lareasonde denegación aparece en el chat como una línea de advertencia. EstablececontinueOnBlock: trueen el hook para, en su lugar, devolver lareasona Claude como el error de la herramienta, para que pueda ajustarse y continuarPostToolUse: por defecto el turno termina y lareasonaparece en el chat como una línea de advertencia. EstablececontinueOnBlock: truepara alimentar lareasonde vuelta a Claude y continuar el turno en su lugarPostToolBatch,UserPromptSubmityUserPromptExpansion: el turno termina y lareasonaparece en el chat como una línea de advertencia
Este ejemplo usa un hook Stop para preguntarle al modelo si todas las tareas solicitadas están completas. Si el modelo devuelve "ok": false porque la condición aún no se cumple, Claude sigue trabajando y usa la reason como su siguiente instrucción:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Check if all tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains to be done\"}."
}
]
}
]
}
}
Para opciones de configuración completas, consulta Hooks basados en prompts en la referencia.
Hooks basados en agentes
[!WARNING]
Los hooks de agente son experimentales. El comportamiento y la configuración pueden cambiar en futuras versiones. Para flujos de trabajo de producción, prefiere hooks de comando.
Cuando la verificación requiere inspeccionar archivos o ejecutar comandos, usa hooks type: "agent". A diferencia de los hooks de prompt que hacen una sola llamada LLM, los hooks de agente generan un subagente que puede leer archivos, buscar código y usar otras herramientas para verificar condiciones antes de devolver una decisión.
Los hooks de agente usan el formato de respuesta "ok" / "reason" con un tiempo de espera predeterminado más largo de 60 segundos y hasta 50 turnos de uso de herramientas. No admiten el campo impossible de los hooks de prompt. En ok: false, Claude Code maneja un hook de agente de la misma manera que maneja un hook de prompt con continueOnBlock: true en el mismo evento, por lo que en PreToolUse y PostToolUse el turno continúa; los hooks de agente no tienen campo continueOnBlock. Consulta configuración de hooks de agente para los campos, incluido el marcador de posición $ARGUMENTS que Claude Code reemplaza con la entrada JSON del hook.
Este ejemplo verifica que las pruebas pasen antes de permitir que Claude se detenga:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
"timeout": 120
}
]
}
]
}
}
Usa hooks de prompt cuando los datos de entrada del hook por sí solos son suficientes para tomar una decisión. Usa hooks de agente cuando necesites verificar algo contra el estado real del código base.
Para opciones de configuración completas, consulta Hooks basados en agentes en la referencia.
HTTP hooks
Usa hooks type: "http" para POST de datos de evento a un punto final HTTP en lugar de ejecutar un comando de shell. El punto final recibe el mismo JSON que un hook de comando recibiría en stdin, y devuelve resultados a través del cuerpo de respuesta HTTP usando el mismo formato JSON.
Los HTTP hooks son útiles cuando quieres que un servidor web, función en la nube o servicio externo maneje la lógica del hook: por ejemplo, un servicio de auditoría compartido que registra eventos de uso de herramientas en un equipo.
Este ejemplo publica cada uso de herramienta a un servicio de registro local:
{
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/tool-use",
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
}
Tu endpoint responde con un cuerpo JSON en el mismo formato de salida que los hooks de comando, y Claude Code también verifica el código de estado de la respuesta:
- Estado 2xx: para bloquear una llamada a herramienta, devuelve los campos
hookSpecificOutputapropiados en el cuerpo. - Cualquier otro estado, o si la solicitud falla: Claude Code informa un error no bloqueante y permite que la acción continúe. Para que un endpoint que falla bloquee la acción, establece
onFailure: "block"en el hook.
Los valores de encabezado soportan interpolación de variables de entorno usando la sintaxis $VAR_NAME o ${VAR_NAME}. Solo las variables listadas en el array allowedEnvVars se resuelven; todas las otras referencias $VAR permanecen vacías.
Para opciones de configuración completas y manejo de respuestas, consulta HTTP hooks en la referencia.
Limitaciones y solución de problemas
Limitaciones
Tenga en cuenta estas restricciones al diseñar hooks:
- Los hooks de comando se comunican solo a través de stdout, stderr y códigos de salida. No pueden activar comandos
/o llamadas a herramientas. El texto devuelto a través deadditionalContextse inyecta como un recordatorio del sistema que Claude lee como texto plano. Los HTTP hooks se comunican a través del cuerpo de respuesta en su lugar. - Los tiempos de espera del hook varían según el tipo. Anule por hook con el campo
timeouten segundos.command,http,mcp_tool: 10 minutos. Claude Code reduce estos valores predeterminados a 30 segundos para los hooksUserPromptSubmit,PreModelSwitchyPostModelSwitch, y a 10 segundos paraMessageDisplay.prompt: 30 segundos.agent: 60 segundos.- Los hooks
SessionEndde cualquier tipo comparten un presupuesto de 1,5 segundos. Si su configuración establece untimeoutpor hook más largo, Claude Code aumenta el presupuesto para que coincida, hasta 60 segundos.
- Los hooks
PostToolUseno pueden deshacer acciones ya que la herramienta ya se ha ejecutado. - Los hooks
PermissionRequestse activan cuando Claude Code está a punto de pedirte permiso, o cuando de otro modo denegaría automáticamente una llamada que no puede mostrar una solicitud. En modo no interactivo con el flag-p, se siguen ejecutando fuera del mododontAsk, y una llamada sobre la que ningún hook decide y que nada más puede responder se deniega. - Los hooks
Stopse activan cada vez que Claude termina de responder, no solo en la finalización de tareas. No se activan en interrupciones del usuario. Los errores de API activan StopFailure en su lugar. - Cuando varios hooks
PreToolUsedevuelvenupdatedInputpara reescribir la entrada de una herramienta, Claude Code no combina las reescrituras ni garantiza con cuál de ellas se ejecuta la herramienta. Evita que más de un hook reescriba la entrada de la misma herramienta.
Hooks y modos de permiso
Con un hook en un archivo de configuración o en hooks/hooks.json de un plugin no puedes relajar las restricciones más allá de lo que permiten las reglas de permisos, y un "deny" de un hook se mantiene en todos los modos de permisos:
- Lo que sigue prevaleciendo sobre
"allow": un hook que devuelve"allow"no elude las reglas de denegación de la configuración, y no puede suprimir la solicitud para:- Lecturas desde rutas de red
- Herramientas MCP marcadas con
requiresUserInteraction - Llamadas a herramientas que una aplicación del Agent SDK marca como
always_ask, a menos que el modo de permisos apruebe la llamada - Herramientas de conectores que tu organización estableció en
asken sesiones donde ese ajuste llega a Claude Code
En modo plan, si tu hook PreToolUse devuelve "allow" para una edición de archivo, obtienes la misma solicitud, bloqueo o aprobación que la edición obtendría sin ningún hook. Para eliminar este límite en las aprobaciones de hooks, aprueba el plan o cambia a otro modo de permisos.
"deny"en todos los modos de permisos: un hook que devuelvepermissionDecision: "deny"bloquea la herramienta incluso en modobypassPermissionso con--dangerously-skip-permissions. Los hooksPreToolUsese activan antes de cualquier verificación de modo de permisos, en cada modo de permisos, incluidodontAsk. Esto te permite aplicar políticas que los usuarios no pueden eludir cambiando su modo de permisos.
[!NOTE]
Si tu máquina no tiene configuración administrada y tu sesión no usa credenciales de
/loginpara un plan Team o Enterprise, un mod puede aprobar una llamada que tu hookPreToolUsebloqueó. Una sesión que se ejecuta con una clave de API o con unCLAUDE_CODE_OAUTH_TOKENde larga duración no está usando ese inicio de sesión, incluso cuando ese inicio de sesión está almacenado.En modo plan, un mod puede aprobar una llamada bloqueada de ese tipo solo cuando es de solo lectura, es decir, no una edición de archivo. Para los casos en los que la aprobación de un mod no supera tus reglas y hooks, consulta Extender permisos con hooks.
Hook no se activa
El hook está configurado pero nunca se ejecuta.
- Ejecuta
/hooksy confirma que el hook aparece bajo el evento correcto - Para un hook en el frontmatter de un skill, invoca el skill o pídele a Claude que lo haga, y luego ejecuta
/hooksde nuevo./hooksno lista los hooks de un skill que no se ha invocado en la sesión. Si/hookssigue sin listar el hook, revisa otras causas, como un frontmatter que no se pudo analizar o uno de los límites de los hooks de skills - Verifique que el patrón del matcher coincida exactamente con el nombre de la herramienta. Los matchers distinguen mayúsculas de minúsculas
- Verifique que esté activando el tipo de evento correcto:
PreToolUsese activa antes de la ejecución de la herramienta,PostToolUsese activa después. Un hookPermissionRequestse activa cuando Claude Code está a punto de pedirle permiso; consulte las limitaciones para los casos no interactivos
Error de hook en la salida
Ves un mensaje como "PreToolUse hook error: ..." en la transcripción.
-
Tu script salió con un código no cero inesperadamente. Pruébalo manualmente canalizando JSON de muestra:
echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh echo $? # Verifica el código de salida -
Si ves "command not found", usa rutas absolutas o
${CLAUDE_PROJECT_DIR}para referenciar scripts. Para evitar entrecomillado de shell por completo, añade"args": []para cambiar a forma exec, que genera el script directamente sin un shell -
Si ves "jq: command not found", instala
jqo usa Python/Node.js para análisis JSON -
Si el mensaje incluye
blocking because Git Bash wasn't found, estás en Windows, Claude Code no encontró Git Bash y el hook que falló o cuyo tiempo de espera se agotó bloqueó la llamada a herramienta. Consulta Hooks fallidos en Windows sin Git Bash para ver las soluciones -
Si el aviso muestra un mensaje de validación JSON, la salida estándar de su hook se analizó como JSON pero falló la validación del esquema. Si muestra un mensaje de análisis JSON, la salida estándar parecía un objeto JSON pero no era JSON válido. Ambos suceden incluso en salida 0.
Para corregir una falla de análisis, construya la carga útil con un codificador JSON como
jqen lugar de concatenación de cadenas, para que las comillas y barras invertidas dentro de los valores se escapen. La sección Exit code output de la referencia cubre las combinaciones de código de salida y JSON -
Si el script no se ejecuta en absoluto, hazlo ejecutable:
chmod +x ./my-hook.sh
/hooks no muestra hooks configurados
Editaste un archivo de configuración pero los hooks no aparecen en el menú.
- Las ediciones de archivos normalmente se recogen automáticamente. Si no han aparecido después de unos segundos, el observador de archivos puede haber perdido el cambio: reinicia tu sesión para forzar una recarga.
- Verifique que su JSON sea válido: las comas finales y comentarios no están permitidos
- Confirma que el archivo de configuración está en la ubicación correcta:
.claude/settings.jsonpara hooks de proyecto,~/.claude/settings.jsonpara hooks globales - Si el menú muestra
Only hooks from managed settings run here, tu organización ha establecidoallowManagedHooksOnly. Los hooks en tus archivos de configuración de usuario, de proyecto y local no se ejecutan ni aparecen en la lista
El hook Stop alcanza el límite de bloqueo
Claude sigue trabajando en lugar de detenerse, luego termina el turno con una advertencia de que el hook Stop bloqueó demasiadas veces consecutivas.
Claude Code sobrescribe un hook Stop después de que bloquea ocho veces seguidas sin ninguna llamada a herramienta de Claude entre medias. Tu script de hook necesita verificar si ya activó una continuación. Analiza el campo stop_hook_active de la entrada JSON y sal temprano si es true:
#!/bin/bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # Permite que Claude se detenga
fi
# ... resto de tu lógica de hook
Si tu hook legítimamente necesita más de ocho iteraciones para converger, aumenta el límite con CLAUDE_CODE_STOP_HOOK_BLOCK_CAP.
Hook JSON no tiene efecto
Su hook imprime JSON válido, pero la decisión no surte efecto y no aparece ningún error en la transcripción. Verifique cuál es la causa que se aplica:
- Salida adicional antes del JSON: algo más escribe en stdout primero, generalmente un
echoincondicional en tu perfil de shell, por lo que la salida ya no comienza con{. Consulta Salida del perfil de shell antes del JSON. - Un campo en el nivel incorrecto: compara la ubicación de cada campo con el formato JSON output. Por ejemplo,
permissionDecisionpertenece dentro dehookSpecificOutput, no en el nivel superior. Consulta Campos en el nivel incorrecto.
Salida del perfil de shell antes del JSON
Los hooks se ejecutan en shells no interactivos, pero Git Bash y algunas configuraciones, como BASH_ENV apuntando a ~/.bashrc, aún cargan tu perfil, y todo lo que el perfil imprime llega a stdout antes del JSON de tu hook:
Shell ready on arm64
{"decision": "block", "reason": "Not allowed"}
A menos que el hook sea asíncrono, Claude Code lee la salida que no comienza con { como texto plano, por lo que tu JSON se ignora. Como el hook salió con 0, la transcripción tampoco muestra ningún error. Para comprobar esta causa, inicia Claude Code con claude --debug, activa el hook y busca en el registro de depuración Hook output does not start with {. Para corregirlo, envuelve las declaraciones echo de tu perfil para que solo se ejecuten en shells interactivos:
# En ~/.zshrc o ~/.bashrc
if [[ $- == *i* ]]; then
echo "Shell ready"
fi
La variable $- contiene banderas de shell, e i significa interactivo. Los hooks se ejecutan en shells no interactivos, por lo que el echo se omite.
Campos en el nivel incorrecto
Cuando su hook devuelve permissionDecision o additionalContext en el nivel superior en lugar de dentro de hookSpecificOutput, el JSON aún se analiza, y Claude Code ignora los campos mal colocados sin reportar un error. Para ver qué campos ignoró, inicie Claude Code con claude --debug y busque en el registro de depuración Hook JSON output had unrecognized keys.
Verificar qué hizo un hook
Presiona Ctrl+O para abrir la vista de transcripción y busca el resultado del hook:
- Éxito: no ves nada, a menos que el JSON del hook muestre algo, como
systemMessageo retroalimentación del hook Stop.- Para confirmar que el hook se ejecutó, verifica su efecto, como un archivo reformateado
- Error de bloqueo: en la mayoría de los eventos ves el mensaje que acompañó al bloqueo, por ejemplo
Blocked: rm commands are not allowed. En algunos eventos, comoConfigChangeyElicitation, no ves ningún mensaje. Exit code 2 explica de dónde proviene el mensaje. - Error sin bloqueo: ves un aviso
<hook name> hook errorcon una explicación breve, como la primera línea de stderr después deFailed with non-blocking status code:, o un mensaje de validación o análisis JSON. La acción siguió adelante.
Para consultar el resultado de un código de salida y una stdout específicos, incluidas las excepciones por evento, consulta Exit code output en la referencia.
Para ver los detalles completos de ejecución, incluidos los códigos de salida, stdout y stderr del hook, lee el registro de depuración. Inicia Claude Code con claude --debug-file /tmp/claude.log para escribir en una ruta conocida y luego ejecuta tail -f /tmp/claude.log en otra terminal. Si iniciaste sin ese flag, ejecuta /debug a mitad de sesión para habilitar el registro y encontrar la ruta del registro.
Aprende más
- Referencia de Hooks: esquemas de eventos completos, formato de salida JSON, hooks asincronos y hooks de herramientas MCP
- Consideraciones de seguridad: revisa antes de desplegar hooks en entornos compartidos o de producción
- Ejemplo de validador de comandos Bash: implementación de referencia completa