Hooks
Los hooks son comandos de shell, prompts LLM, agentes multi-turno o llamadas HTTP que se ejecutan en respuesta a eventos de Claude Code. Pueden bloquear llamadas a herramientas, post-procesar resultados, transformar prompts, auto-formatear archivos y controlar permisos programáticamente.
2 bloquea la operación y envía stderr al modelo.
Fuentes de Hooks (Orden de Prioridad)
| # | Fuente | Ubicación | Alcance |
|---|---|---|---|
| 1 | Política (managed) | Configuración gestionada | Empresa completa, máxima autoridad |
| 2 | Configuración de usuario | ~/.claude/settings.json | Personal, todos los proyectos |
| 3 | Configuración de proyecto | .claude/settings.json | Compartida con el equipo |
| 4 | Configuración local | .claude/settings.local.json | Privada por proyecto |
| 5 | Hooks de plugin | ~/.claude/plugins/*/hooks/hooks.json | Con alcance al plugin |
| 6 | Hooks de sesión | En memoria (desde skills/agentes) | Temporales, solo sesión actual |
| 7 | Hooks de función | En memoria (SDK/plugins) | Validación programática |
Todos los hooks que coincidan para un evento dado se ejecutan en paralelo.
Los 27 Eventos de Hook
Ciclo de vida de herramientas
| PreToolUse | Antes de ejecutar la herramienta. Puede bloquear o modificar la entrada. |
| PostToolUse | Después de que la herramienta tiene éxito. Puede inyectar contexto. |
| PostToolUseFailure | Después de que la herramienta falla o es interrumpida. |
Sesión
| SessionStart | startup / resume / clear / compact |
| SessionEnd | clear / resume / logout / exit / other |
| UserPromptSubmit | Cuando el usuario envía un prompt. Puede bloquear. |
| Stop | Turno completado (sin más llamadas a herramientas). |
| StopFailure | El turno falló con un error. |
| Setup | Configuración única: init / maintenance. |
Permisos
| PermissionRequest | Antes de mostrar el diálogo de permiso. Puede auto-aprobar. |
| PermissionDenied | Después de que el usuario deniega. Puede reintentar. |
| Notification | Cuando Claude envía una notificación. |
Subagentes y Equipos
| SubagentStart | Cuando se lanza un subagente. |
| SubagentStop | Cuando un subagente completa. |
| TeammateIdle | Un compañero no tiene trabajo. |
| TaskCreated | Tarea creada en un equipo. |
| TaskCompleted | Tarea completada. |
Contexto
| PreCompact | Antes de la compactación. manual / auto. |
| PostCompact | Después de la compactación. Incluye resumen. |
| InstructionsLoaded | Cuando se cargan CLAUDE.md / archivos de memoria. |
| ConfigChange | Archivos de configuración cambiados. |
MCP y Sistema de Archivos
| Elicitation | Un servidor MCP solicita entrada del usuario. |
| ElicitationResult | Después de respuesta a elicitación MCP. |
| WorktreeCreate | Worktree de git creado. |
| WorktreeRemove | Worktree de git eliminado. |
| CwdChanged | Directorio de trabajo cambiado. |
| FileChanged | Un archivo vigilado ha cambiado. |
Los 4 Tipos de Hook
command — el más común
{
"type": "command",
"command": "prettier --write "$FILE"",
"if": "Write|Edit",
"shell": "bash",
"timeout": 30,
"statusMessage": "Formateando...",
"once": false,
"async": false,
"asyncRewake": false
} timeout — segundos (por defecto: 600)once — auto-eliminar tras la primera ejecuciónasync — ejecutar en segundo plano sin bloquearasyncRewake — segundo plano + despertar al modelo en exit 2prompt — evaluación con LLM
{
"type": "prompt",
"prompt": "Revisa por problemas de seguridad: $ARGUMENTS",
"if": "Write|Edit",
"timeout": 30,
"model": "claude-sonnet-4-6",
"statusMessage": "Revisando..."
} $ARGUMENTS — reemplazado con el JSON de entrada completomodel — por defecto modelo pequeño y rápidotimeout — por defecto: 30 segundosagent — verificación multi-turno
{
"type": "agent",
"prompt": "Verifica que los tests siguen pasando: $ARGUMENTS",
"timeout": 60,
"model": "claude-sonnet-4-6"
} model — por defecto Haikutimeout — por defecto: 60 segundoshttp — webhook / API externa
{
"type": "http",
"url": "https://api.example.com/hooks/claude",
"headers": {
"Authorization": "Bearer $API_TOKEN"
},
"allowedEnvVars": ["API_TOKEN"],
"timeout": 30
} allowedEnvVars — lista blanca para expansión $VAR en headersFormato de Configuración
Estructura de settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "prettier --check "$FILE"",
"timeout": 10
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "notificar-fin.sh"
}
]
}
]
}
} Hooks en frontmatter de skill
---
hooks:
PostToolUse:
- matcher: "Write|Edit"
hooks:
- type: command
command: "prettier --write $FILE"
--- Se registran como hooks de sesión en memoria. Activos solo durante la invocación del skill (o la sesión si once: false).
Hooks en frontmatter de agente
Mismo formato YAML. Los hooks Stop se convierten automáticamente en hooks SubagentStop, con alcance al ID del agente, y se limpian cuando el agente termina.
Entrada (stdin) y Salida (stdout)
Campos base (todos los eventos)
{
"session_id": "abc123",
"transcript_path": "/ruta/al/transcript.jsonl",
"cwd": "/ruta/al/proyecto",
"hook_event_name": "PreToolUse",
"permission_mode": "default",
"agent_id": "agent-xyz",
"agent_type": "general-purpose"
} Adiciones por evento clave
tool_name, tool_input, tool_use_id // PostToolUse añade: tool_response // PostToolUseFailure añade: error, is_interrupt
source, agent_type, model
tool_name, tool_input, permission_suggestions
file_path, event // FileChanged old_cwd, new_cwd // CwdChanged
Salida: texto plano o JSON
Si stdout empieza con {, se parsea como JSON. Si no, se muestra como texto plano en el transcript.
{
"continue": true,
"suppressOutput": false,
"stopReason": "mensaje",
"decision": "approve|block",
"reason": "explicación",
"systemMessage": "mostrado al usuario",
"hookSpecificOutput": { }
} hookSpecificOutput por evento
permissionDecision: "allow|deny|ask"
updatedInput: { } // modificar la entrada
additionalContext: "" // inyectado como contexto additionalContext: ""
updatedMCPToolOutput: { } // reemplazar salida MCP additionalContext: "" initialUserMessage: "" // mensaje auto-inyectado watchPaths: ["/ruta/"] // configurar watchers
decision: {
behavior: "allow|deny",
updatedPermissions: [
{ tool: "Bash(npm*)", behavior: "allow" }
]
} Códigos de Salida
| Código | Significado | Comportamiento |
|---|---|---|
| 0 | Éxito | Stdout mostrado en transcript o parseado como JSON |
| 2 | Error bloqueante | Bloquea la operación. Stderr enviado al modelo como feedback. |
| otro | Error no bloqueante | Mostrado al usuario únicamente, no bloquea la acción |
| Evento | Comportamiento con exit code 2 |
|---|---|
| PreToolUse | Bloquea la llamada a la herramienta. Stderr mostrado al modelo. |
| PostToolUse | Muestra stderr al modelo inmediatamente como feedback. |
| UserPromptSubmit | Bloquea el procesamiento, limpia el prompt, muestra stderr al usuario. |
| Stop | Muestra stderr al modelo y continúa la conversación. |
| SessionStart | Los errores bloqueantes se ignoran — la sesión comienza igualmente. |
Matching y Filtro if
Campo matcher (3 modos)
| Modo | Ejemplo |
|---|---|
| Exacto | "Write" |
| Separado por barras | "Write|Edit" |
| Regex | "^Write.*" o ".*" |
Sin matcher = se activa para TODAS las instancias de ese evento. Comparado contra tool_name, source, agent_type, etc. según el evento.
Condición if — filtro secundario
Usa la sintaxis de reglas de permisos. Se evalúa antes de lanzar el proceso del hook. Evita creación innecesaria de procesos.
// Solo comandos git
"if": "Bash(git *)"
// Solo npm publish
"if": "Bash(npm publish:*)"
// Solo escrituras TypeScript
"if": "Write(*.ts)"
// Solo ediciones en api/
"if": "Edit(src/api/*)"
Integración con Permisos
Precedencia de permisos en PreToolUse
Hook dice "allow"
+ regla deny existe → DENY (la regla gana)
+ regla ask existe → Forzar diálogo de permiso
+ sin reglas → ALLOW ✓
Hook dice "deny" → DENY inmediato
Hook dice "ask" → Forzar diálogo de permiso
Múltiples hooks: deny > ask > allow Un hook allow NO anula reglas deny/ask de settings.json. Las reglas siempre ganan.
PermissionRequest — auto-aprobación en CI
Se activa antes de que aparezca el diálogo de permiso. Los hooks pueden auto-aprobar o auto-denegar sin interacción humana.
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedPermissions": [
{ "tool": "Bash(npm test:*)",
"behavior": "allow" }
]
}
}
} Hooks Asíncronos
Async por configuración
{
"type": "command",
"command": "tarea-lenta.sh",
"async": true
} El hook se ejecuta en segundo plano. Claude continúa sin esperar.
Declaración async en tiempo de ejecución
El hook se declara async en tiempo de ejecución mediante su primera línea en stdout:
{"async": true, "asyncTimeout": 30} asyncRewake
{
"type": "command",
"command": "verificar-ci.sh",
"async": true,
"asyncRewake": true
} Cuando este hook async termina con código 2, despierta al modelo via enqueuePendingNotification(). Permite patrones de monitoreo en segundo plano.
Variables de Entorno y Timeouts
Disponibles para hooks de comando
| CLAUDE_PROJECT_DIR | Directorio raíz del proyecto (estable, no ruta de worktree) |
| CLAUDE_PLUGIN_ROOT | Directorio raíz del plugin/skill |
| CLAUDE_PLUGIN_DATA | Directorio de datos del plugin |
| CLAUDE_PLUGIN_OPTION_* | Valores de opciones del plugin (en MAYÚSCULAS) |
| CLAUDE_ENV_FILE | Ruta a archivo .sh para variables de entorno de BashTool. Solo en SessionStart, Setup, CwdChanged, FileChanged. |
| CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS | Anular timeout de SessionEnd (por defecto: 1500ms) |
Timeouts por defecto
| General (command / http) | 10 min |
| Hooks de SessionEnd | 1,5 seg |
| Hooks de prompt | 30 seg |
| Hooks de agente | 60 seg |
| Hooks async (asyncTimeout) | 15 seg |
| Hooks de función | 5 seg |
El campo timeout en la configuración del hook está en segundos.
Políticas de seguridad
allowManagedHooksOnly — solo se ejecutan hooks de políticadisableAllHooks — en policySettings: deshabilita todos; en otras fuentes: solo los no gestionadosElicitación Interactiva de Prompts
Los hooks de comando pueden solicitar entrada del usuario mediante un protocolo stdout/stdin — habilitando hooks interactivos de múltiples pasos sin una UI separada.
El hook escribe en stdout:
{
"prompt": "request-id-123",
"message": "Elige el destino de deploy:",
"options": [
{ "key": "staging", "label": "Staging" },
{ "key": "production", "label": "Producción" }
]
} Claude Code escribe de vuelta al stdin del hook:
{
"prompt_response": "request-id-123",
"selected": "staging"
} Ejemplos Prácticos
Auto-formatear después de ediciones
// PostToolUse, matcher: "Write|Edit"
{
"type": "command",
"command": "prettier --write "$(cat | jq -r '.tool_input.file_path')"",
"timeout": 10,
"statusMessage": "Formateando..."
} Bloquear comandos git peligrosos
// PreToolUse, matcher: "Bash", if: "Bash(git *)"
{
"type": "command",
"command": "CMD=$(cat|jq -r '.tool_input.command'); if echo "$CMD" | grep -qE 'force|reset --hard'; then echo 'Bloqueado' >&2; exit 2; fi"
} Auto-aprobar permisos en CI
// PermissionRequest, matcher: "Bash"
{
"type": "command",
"command": "echo '{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow"}}}'"
} Monitoreo CI en segundo plano (asyncRewake)
// Stop event
{
"type": "command",
"command": "verificar-estado-ci.sh",
"async": true,
"asyncRewake": true
}
// Cuando CI falla, el script sale con 2 → despierta al modelo .claude/settings.json de un proyecto podría ejecutar código arbitrario.
Verifica siempre los hooks a nivel de proyecto antes de aceptar la confianza del workspace, y usa
allowManagedHooksOnly en entornos empresariales para restringir
los hooks únicamente a fuentes controladas por política.