Hooks
Los hooks son scripts automatizados que se ejecutan en respuesta a eventos específicos durante las sesiones de Claude Code. Permiten la automatización, validación, gestión de permisos y flujos de trabajo personalizados.
Introducción
Los hooks son acciones automatizadas (comandos shell, webhooks HTTP, avisos LLM, llamadas a herramientas MCP o evaluaciones de subagentes) que se ejecutan automáticamente cuando ocurren eventos específicos en Claude Code. Reciben la entrada JSON y comunican resultados a través de códigos de salida y salida JSON.
Características principales: - Automatización basada en eventos - Entrada/salida basada en JSON - Soporte para command, http, mcp_tool, prompt, y agent tipos de hook - Patrón de coincidencia para hooks específicos de la herramienta
Configuración
Los hooks se configuran en archivos de configuración con una estructura específica:
~/.claude/settings.json- Configuración del usuario (todos los proyectos).claude/settings.json- Configuración del proyecto (compartible, comprometido).claude/settings.local.json- Configuración local del proyecto (no comprometida)- Políticas gestionadas - Configuración en toda la Organización
- Complemento
hooks/hooks.json- Hooks con capa de complemento - Habilidad/Agente frontal - Hooks de vida de componentes
Estructura básica de configuración
{
"hooks": {
"EventName": [
{
"matcher": "ToolPattern",
"hooks": [
{
"type": "command",
"command": "your-command-here",
"timeout": 60
}
]
}
]
}
}
Campos clave:
| Campo | Descripción | Ejemplo |
|---|---|---|
matcher |
Patrón para emparejar nombres de herramientas (sensible a mayúsculas y minúsculas) | "Write", "Edit\|Write", "*" |
hooks |
Conjunto de definiciones de hooks | [{ "type": "command", ... }] |
type |
Tipo de hook: "command" (bash), "prompt" (LLM), "http" (webhook), "mcp_tool" (Invocación de la herramienta MCP, v2.1.118+), o "agent" (subagente) |
"command" |
command |
Orden Shell a ejecutar | "$CLAUDE_PROJECT_DIR/.claude/hooks/format.sh" |
timeout |
Tiempo de espera opcional en segundos. Predeterminado: 600 para comando/http/mcp_tool, 30 para prompt, 60 para agente. | 30 |
once |
Si true, el hook se elimina después de su primera ejecución exitosa (falla/bloqueado/tiempo-salida se mantiene). frontmatter habilidad solamente; ignorado en la configuración y agente frontmatter |
true |
async |
Si true, se ejecuta en segundo plano sin bloquear |
true |
asyncRewake |
Si true, se ejecuta en el fondo y despierta a Claude en el código de salida 2. Implica async. |
true |
shell |
Acepta "bash" o "powershell". Predeterminados "bash", o "powershell" En Windows cuando Git Bash no está instalado. El código de salida de un PowerShell se bloquea sólo desde v2.1.309; actualice antes de confiar en un hook de protección PowerShell. |
"bash" |
statusMessage |
Se muestra el mensaje del spinner personalizado mientras se ejecuta el hook | "Formatting…" |
onFailure |
¿Qué sucede con la acción cuando falla un comando o HTTP hook (no se puede iniciar, se puede salir, sale con un código inesperado): "continue" (por defecto) o "block" (v2.1.295+) |
"block" |
Nota: Algunos eventos reducen el tiempo de espera por defecto.
UserPromptSubmitbaja lacommand,http, ymcp_toolpor defecto hasta 30 segundos, yMessageDisplaylo reduce a 10 segundos.SessionEndlos hooks comparten un presupuesto de 1,5 segundos; si sus ajustes establecen un más largo por hooktimeout, Claude Code eleva ese presupuesto para igualar, hasta 60 segundos.
Patrones de emparejamiento
| Patrón | Descripción | Ejemplo |
|---|---|---|
| Cadena exacta | Coincide con la herramienta específica | "Write" |
| Patrón Regex | Coincide con múltiples herramientas | "Edit\|Write" |
| Separados por comas | Coincide con cualquier herramienta listada (v2.1.191+) | "Write,Edit" |
| Comodín | Coincide con todas las herramientas | "*" o "" |
| Herramientas MCP | Servidor y patrón de herramientas | "mcp__memory__.*" |
Las coincidencias se corresponden exactamente (v2.1.195+). Un identificador de guión (por ejemplo, un nombre de herramienta MCP que contiene un guión) ya no subcadena accidentalmente coincide con una herramienta diferente.
"Write,Edit"fuego en cualquier herramienta de la lista — construcciones anteriores silenciosamente nunca los disparó.
InstruccionesValores de emparejamiento:
| Valor de emparejamiento | Descripción |
|---|---|
session_start |
Instrucciones cargadas al inicio de la sesión |
nested_traversal |
Instrucciones cargadas durante el recorrido del directorio anidado |
path_glob_match |
Instrucciones cargadas a través de la trayectoria patrón glob coincidente |
Acortamiento con if condiciones (trayectorias de la herramienta-argumento)
Los matcher campo selecciona un hook por nombre de la herramienta ("Write", "Edit|Write", "*") Filtrar más estrechamente por la herramienta argumentos — por ejemplo, ejecutar un hook sólo cuando una edición toque src/, o para guardar lecturas de archivos secretos — añadir un if condición a un controlador de hook individual. Esto es distinto del comparador de nombre de la herramienta: el matcher decide qué herramienta, el if decide que llaman.
if usos sintaxis de regla-permiso (ToolName(pattern)), evaluado contra el nombre de la herramienta y sus argumentos juntos. Read/Edit/Write, el patrón de ruta sigue la semántica gitignore, con los mismos anclajes que las reglas de permiso: un nombre desnudo como .env coincide a cualquier profundidad, src/** es relativo al directorio actual, /src/** a la raíz del proyecto, ~/... a su directorio personal, y //... es una ruta absoluta del sistema de archivos.
Los if campo se sienta en el nivel de hook — un hermano de type y command, dentro de la hooks array — no activado matcher:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"if": "Edit(src/**)",
"command": "./hooks/lint-src.sh"
}
]
},
{
"matcher": "Read",
"hooks": [
{
"type": "command",
"if": "Read(.env)",
"command": "./hooks/block-secret-read.sh"
}
]
}
]
}
}
Ejemplos de validez if patrones: Edit(src/**) (edición src/), Read(~/.ssh/**) (leídos de cualquier clave SSH), Read(.env) (cualquier .env en o por debajo del directorio actual), Bash(git push *) (únicamente git push subcomandos).
Actualización v2.1.214: Un solo segmento
dir/**patrón en un hookifafección (comoEdit(src/**)) ahora sólo coincide<cwd>/dir— no ese directorio a cualquier profundidad en el árbol.src/**también coincide confoo/src/**Uso**/dir/**si necesitas cualquier coincidencia de profundidad. Importante: este estrechamiento se aplica sólo al hookif:condiciones y permitir la aprobación automática — las reglas de denegación/pregunta de permisos siguen siendo igualesdir/**a cualquier profundidad.
Tipos de hook
Claude Code admite cinco tipos de hook:
Hooks de comando
El tipo de hook predeterminado. Ejecuta un comando shell y se comunica a través de códigos JSON stdin/stdout y out.
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate.py\"",
"timeout": 60
}
Formulario ejecutivo (args)
Añadido en v2.1.139.
En lugar de la forma-shell "command": "...", un hook de comando puede generar un binario directamente a través execve() con una args Array. No hay análisis de shell, por lo que los marcadores de posición de ruta nunca necesitan cita y la configuración es inmune a los errores de inyección de shell.
{
"type": "command",
"command": "python3",
"args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/validate.py", "--strict"],
"timeout": 60
}
command siempre es necesario: cuando args está establecido, command es el ejecutable a desovar (resuelto en PATH) y args es su vector de argumento. Omite args Cuando necesites tuberías, redireccionamiento, && Encadenamiento, o expansiones de la shell; uso args cuando se llama a un binario con argumentos.
Hooks HTTP
Añadido en v2.1.63.
Endpoints remotos que reciben la misma entrada de JSON que los hooks de comando. HTTP engancha POST JSON a la URL y recibe una respuesta JSON. Los hooks HTTP se enrutan a través de la caja de arena cuando se habilita sandboxing. allowedEnvVars lista de seguridad.
{
"hooks": {
"PostToolUse": [{
"matcher": "Write",
"hooks": [{
"type": "http",
"url": "https://my-webhook.example.com/hook"
}]
}]
}
}
Propiedades clave:
- "type": "http" -- identifica esto como un hook HTTP - "url" -- el URL de endpoint del Webhook - Enrutado a través de sandbox cuando está habilitado sandbox - Requiere explícito allowedEnvVars para cualquier interpolación variable de entorno en headers valores
Garfios rápidos
LLM-evaluado indica donde el contenido del hook es un prompt que Claude evalúa. Stop y SubagentStop eventos para la comprobación inteligente de la finalización de tareas.
{
"type": "prompt",
"prompt": "Evaluate if Claude completed all requested tasks.",
"timeout": 30
}
El LLM evalúa el prompt y devuelve una decisión estructurada (ver Garfios rápidos para más detalles).
Hooks de herramientas MCP
Añadido en v2.1.118.
Los mcp_tool type invoca una herramienta MCP configurada directamente; la configuración hace referencia al servidor MCP y al nombre de la herramienta en lugar de a un comando shell o URL. Esto es útil cuando la lógica de validación o reacción ya vive en un servidor MCP que ha configurado.
{
"matcher": "Edit",
"hooks": [{
"type": "mcp_tool",
"server": "my-mcp-server",
"tool": "validate_edit",
"input": { "file_path": "${tool_input.file_path}" }
}]
}
Propiedades clave:
- "type": "mcp_tool" -- identifica esto como un hook de herramienta MCP - "server" -- nombre del servidor MCP configurado - "tool" -- el nombre de la herramienta en ese servidor para invocar - "input" -- argumentos opcionales pasados a la herramienta; soporte de valores de cadena ${path} sustitución de la entrada JSON del hook, como "${tool_input.file_path}"
Los argumentos de la herramienta provienen de input (con ${path} sustitución), no desde la entrada de hook directamente. Ver Configuración del servidor MCP para configurar servidores MCP.
Agente Garfios
Los hooks de verificación basados en subagentes que generan un agente dedicado a evaluar condiciones o realizar comprobaciones complejas. A diferencia de los hooks rápidos (evaluación LLM de un solo giro), los hooks de agente pueden utilizar herramientas y realizar razonamientos de varios pasos.
Nota: Los hooks de agente son experimentales y pueden cambiar.
NotaDesde v2.1.280, los hooks de agente no funcionan
PermissionRequest— su respuesta nunca podría permitir o denegar la solicitud. Utilice un comando o HTTP hook que devuelve un objeto de decisión allí (un prompt hook'sok: falseno tiene efecto sobrePermissionRequest).
{
"type": "agent",
"prompt": "Verify the code changes follow our architecture guidelines. Check the relevant design docs and compare.",
"timeout": 120
}
Propiedades clave:
- "type": "agent" -- identifica esto como un hook de agente - "prompt" -- la descripción de la tarea para el subagente - El agente puede utilizar herramientas (Lee, Grep, Bash, etc.) para realizar su evaluación - Devuelve una decisión estructurada similar a los hooks de prompt
Eventos de hook
Claude Code soporta 33 eventos de hook:
| Evento | Cuando se activa | Entrada de emparejamiento | Puede bloquear | Uso común |
|---|---|---|---|---|
| SessionStart | La sesión comienza/resume/clear/compact | startup/resume/clear/compact/fork | No | Configuración del entorno |
| Configuración | --init-onlyo bien --init/--maintenance con -p (CI/preparación del guión) |
init/maintenance |
No | Suministro de herramientas, instalar deps |
| InstruccionesCargado | Después de CLAUDE.md o el archivo de reglas cargado | Motivo de la carga (session_start/nested_traversal/path_glob_match/include/compact) |
No | Auditoría/registro de observabilidad |
| UserPromptSubmit | El usuario envía el aviso | (ninguno) | Sí. | Validar indicaciones |
| UserPromptExpansion | El comando Slash tipo usuario o el prompt MCP se expande en un prompt | Nombre de la orden | Sí. | Transformar o inspeccionar el prompt ampliado |
| PreToolUse | Antes de ejecutar la herramienta | Nombre de la herramienta | Sí (admitir/negar/preguntar/demorar) | Validar, modificar entradas |
| PermissionRequest | Se muestra el diálogo de permisos | Nombre de la herramienta | Sí. | Auto-approve/deny |
| PermisoDenegado | El modo automático niega una llamada a la herramienta | Nombre de la herramienta | No | Registro, análisis, aplicación de políticas |
| PostToolUse | Después de que la herramienta tenga éxito | Nombre de la herramienta | No | Añadir contexto, retroalimentación |
| PostherramientaUsoFailure | Fallo en la ejecución de la herramienta | Nombre de la herramienta | No | Manejo de errores, registro |
| PostToolBatch | Después de que un lote de usos de herramienta se completa | (ninguno) | Sí. | Notificación agregada, validación por lotes |
| Notificación | Notificación enviada | Tipo de notificación | No | Comunicaciones personalizadas |
| Mostrar mensaje | Mientras se muestra el texto del mensaje asistente | (ninguno) | No | Transformar u ocultar el texto de mensaje mostrado (v2.1.152) |
| SubagentStart | Subagente desovado | Nombre del tipo de agente | No | Configuración de los subagentes |
| SubagentStop | Acabados de subagentes | Nombre del tipo de agente | Sí. | Validación de subagentes |
| Stop | Claude termina de responder | (ninguno) | Sí. | Verificación de la finalización de las tareas |
| Detener fallos | Fin del error API | Tipo de error (rate_limit, overloaded, server_error, …) |
No | Recuperación de errores, registro |
| TeammateIdle | Agente equipo compañero de equipo inactivo | (ninguno) | Sí. | Coordinación entre los compañeros de equipo |
| Tarea completada | Tarea marcada completa | (ninguno) | Sí. | Medidas posteriores a las tareas |
| TareaCreada | Tarea creada a través de TaskCreate | (ninguno) | Sí. | Seguimiento de tareas, registro |
| ConfigChange | Cambios en el archivo de configuración | Fuente de configuración (user_settings/project_settings/local_settings/policy_settings/skills) |
Sí (excepto la política) | Reaccionar a las actualizaciones de configuración |
| CwdChanged | Cambios en el directorio de trabajo | (ninguno) | No | Configuración específica del directorio |
| Added | Nuevo directorio de trabajo registrado a mediados de la sesión vía /add-dir o el SDK register_repo_root solicitud de control (v2.1.219) |
slash_command/register_repo_root |
No | Configurar herramientas para un directorio recién añadido |
| ArchivoCambiado | Cambios de archivo observados | Nombres de archivo a ver (por ejemplo: .envrc\|.env) |
No | Monitorización de archivos, reconstrucción |
| Precompact | Antes de la compactación del contexto | manual/auto | Sí. | Acciones precompactas |
| Postcompact | Después de la compactación completa | (ninguno) | No | Medidas posteriores al pacto |
| PreModelSwitch | Antes de que Claude Code aplique un interruptor de modelo solicitado | Nombre canónico del modelo que se cambia a (desde to_model) |
Sí. | Cambios en el modelo de puerta o veto |
| PostModelSwitch | Después de los cambios en el modelo de la sesión, incluyendo los cambios que hace Claude Code (como restaurar el modelo en curriculum vitae) | Nombre canónico del modelo cambiado a (desde to_model) |
No | Registrar o reaccionar a los cambios de modelo |
| WorktreeCreate | Se está creando Worktree | (ninguno) | Sí (retorno de ruta) | Inicialización de Worktree |
| WorktreeRemove | Se retira el árbol de trabajo | (ninguno) | Sí (salida no cero falla la eliminación) | Limpieza de árboles de trabajo |
| Eliminación | Servidor MCP solicita la entrada del usuario | Nombre del servidor MCP | Sí. | Validación de entradas |
| Resultado de la eliminación | El usuario responde a la excitación | Nombre del servidor MCP | Sí. | Procesamiento de respuestas |
| SessionEnd | Termina la sesión | Motivo final (clear/resume/logout/prompt_input_exit/other) |
No | Limpieza, registro final |
PreModelSwitch y PostModelSwitch requieren v2.1.251 o posterior. Ambos reciben from_model y to_model; el comparador se evalúa contra el nombre canónico derivado de to_model (por ejemplo: claude-opus-5, .*opus.*). command, http, y mcp_tool timeout por defecto se baja a 30 segundos.
TaskCreatedyTaskCompletednecesita las herramientas de todo habilitado (v2.1.233). Estos dos eventos se disparan desde las herramientas de seguimiento de tareas y tareas (TaskCreate/Get/Update/List,TodoWrite), que son disponible por defecto sólo en los modelos Claude 3.x, Opus 4 a 4.7, Sonnet 4 a 4.6, y Haiku 4.5. En otros modelos los hooks siguen siendo una configuración válida, pero simplemente nunca se dispara — no obtiene ninguna salida y ningún error.CLAUDE_CODE_ENABLE_TODO_TOOLS=1traer las herramientas, y por lo tanto los acontecimientos, de vuelta.Duración de PostToolUse (v2.1.119):
PostToolUseyPostToolUseFailurelas entradas de hook ahora incluyenduration_ms— véase la PostToolUse sección para más detalles.
PreToolUse
Ejecuta después de que Claude crea parámetros de la herramienta y antes de procesarla. Utilice esto para validar o modificar las entradas de la herramienta.
Configuración:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py"
}
]
}
]
}
}
Coincidencias comunes: Agent, Bash, Glob, Grep, Read, Edit, Write, WebFetch, WebSearch
Control de salida:
- permissionDecision: "allow", "deny", "ask"o bien "defer"
- "allow" omite el aviso de permiso (excepto para las herramientas que requieren interacción del usuario, y las herramientas de conectores de su organización ask)
- "deny" evita la llamada de la herramienta - "ask" pide al usuario que confirme; los nombres del prompt el plugin o la habilidad del hook viene de (v2.1.301) - "defer" termina el giro para que la herramienta se pueda reanudar más tarde, y funciona sólo en modo no interactivo con -p (las sesiones interactivas registran una advertencia y la ignoran); el resultado lleva stop_reason: "tool_deferred". permissionDecisionReason, updatedInput y additionalContext se ignoran todos para este valor - Negar y pedir reglas todavía se evalúan independientemente de lo que el hook devuelve. Cuando múltiples PreToolUse hooks no están de acuerdo, precede es deny > defer > ask > allow
- permissionDecisionReason: Explicación para la decisión. "ask"; mostrado a Claude para "deny"; para "allow" y "defer", escrito sólo para el registro de depuración - updatedInput: Entrada de herramienta de reemplazo (reemplaza todo el objeto de entrada). Desde v2.1.300 se valida como la propia entrada de Claude — las claves desconocidas se sueltan, y la entrada que la herramienta rechaza niega la llamada. PreToolUse retorno de hooks updatedInput Para la misma llamada, Claude Code no fusiona las reescrituras ni garantiza con cuál de las herramientas se ejecuta, así que deja que sólo un hook reescripe la entrada de una herramienta dada.
PostToolUse
Se ejecuta inmediatamente después de completar la herramienta. Utilícelo para verificación, registro o proporcionar contexto a Claude.
Configuración:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/security-scan.py"
}
]
}
]
}
}
Control de salida:
- "block" decisión le pide a Claude con retroalimentación - additionalContext: Contexto añadido para Claude
Campos de entrada adicionales (v2.1.119):
| Campo | Tipo | Descripción |
|---|---|---|
duration_ms |
número | Tiempo de ejecución de la herramienta en milisegundos. Excluye el tiempo pasado en avisos de permisos y ejecución de hook PreToolUse. Disponible en ambos PostToolUse y PostToolUseFailure Hooks. |
Bloques recuperables (continueOnBlock, v2.1.139)
Un hook de comando que devuelve "decision": "block" el PostToolUse añade la reason junto al resultado de la herramienta; Claude todavía ve la salida original. continueOnBlock es una pronto-hook field (los hooks de mando y agente no tienen ninguno): por defecto, un prompt hook que devuelve ok: false el PostToolUse termina el giro y muestra la razón como una línea de advertencia. "continueOnBlock": true en el prompt hook para alimentar la razón de nuevo a Claude y continuar el turno, por lo que el modelo puede leer la retroalimentación y volver a intentar o ajustar.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "prompt",
"prompt": "Check this edit against our write policy (for example, read-only paths). Hook input: $ARGUMENTS",
"continueOnBlock": true
}
]
}
]
}
}
Usa esto cuando el hook reason es algo que Claude puede actuar (por ejemplo, "este archivo es de sólo lectura; escriba en otro lugar"); déjelo fuera cuando un bloque debe detener el giro por completo.
UserPromptSubmit
Se ejecuta cuando el usuario envía un prompt, antes de que Claude lo procese. Desde v2.1.299 se ejecuta antes de que se amplíe una instrucción de habilidad o personalizada, por lo que bloquear el prompt también detiene ese comando ! líneas de protección; UserPromptExpansion fuegos después de él.
Configuración:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-prompt.py"
}
]
}
]
}
}
Control de salida:
- decision: "block" para evitar el procesamiento - reason: Explicación si está bloqueada - additionalContext: Contexto añadido al prompt
Parar y SubagentStop
Ejecute cuando Claude termine de responder (Parar) o un subagente complete (SubagentStop). Soporta una evaluación basada en la rapidez para la comprobación inteligente de la finalización de tareas.
Campo de entrada adicional: Ambos Stop y SubagentStop los hooks reciben un last_assistant_message campo en su entrada JSON, que contiene el mensaje final de Claude o el subagente antes de detenerse. Esto es útil para evaluar la finalización de la tarea.
Configuración:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Evaluate if Claude completed all requested tasks.",
"timeout": 30
}
]
}
]
}
}
Tapa de seguridad en bloques consecutivos (v2.1.143): Si a
Stopretornos de hook"decision": "block"(o conjuntoscontinue: false) 8 veces seguidas Para el mismo turno, Claude Code anula el siguiente bloque y termina el giro.CLAUDE_CODE_STOP_HOOK_BLOCK_CAP=<integer>(configurado a0para deshabilitar el capuchón por completo). Esto evita que un buggy Detenga el hook de bucle para siempre.
Campo de retorno (v2.1.163): A Stop o SubagentStop Hook puede volver hookSpecificOutput.additionalContext para dar Claude retroalimentación y continuar el giro sin aparecer una etiqueta de error. Anteriormente, influir en el modelo desde un hook de parada era incómodo; ahora el hook puede inyectar el contexto limpiamente, evitando el comportamiento de la etiqueta de error de las rutas de retroalimentación más antiguas (como "decision": "block").
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "Reminder: run the test suite before declaring done."
}
}
SubagentStart
Se ejecuta cuando un subagente comienza la ejecución. La entrada de emparejador es el nombre de tipo de agente, permitiendo que los hooks se dirijan a tipos específicos de subagente.
Configuración:
{
"hooks": {
"SubagentStart": [
{
"matcher": "code-review",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/subagent-init.sh"
}
]
}
]
}
}
SessionStart
Se ejecuta cuando la sesión comienza o se reanuda. Puede persistir variables de entorno.
Coincidencias: startup, resume, clear, compact, fork
Actualización v2.1.214: Una sesión bifurcada ahora informa fuente
"fork"— ha informado anteriormente"resume".
Particularidad: Uso CLAUDE_ENV_FILE para persistir en las variables de entorno (también disponible en CwdChanged y FileChanged Hooks:
#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=development' >> "$CLAUDE_ENV_FILE"
fi
exit 0
Salidas de la sesión (v2.1.152): A SessionStart hook puede devolver JSON a las skills de re-escanear y establecer el título de la sesión:
{
"reloadSkills": true,
"hookSpecificOutput": {
"sessionTitle": "Payments migration"
}
}
Alto nivel reloadSkills: true activa una habilidad re-escanear en la misma sesión (la misma acción que el /reload-skills comando), haciendo que las skills del hook acaba de instalarse disponible inmediatamente. hookSpecificOutput.sessionTitle establece el título de visualización de la sesión al iniciar y reanudar.
SessionEnd
Se ejecuta cuando la sesión termina para realizar limpieza o registro final. No se puede bloquear la terminación.
Valores de campo de la razón:
- clear - Usuario despejó la sesión - resume - Sesión conmutada a través de interactivo /resume
- logout - Usuario que ha salido - prompt_input_exit - Usuario salido a través de entrada rápida - other - Otra razón
Configuración:
{
"hooks": {
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-cleanup.sh\""
}
]
}
]
}
}
Evento de notificación
Actualizado coincidentes para eventos de notificación: - permission_prompt - Notificación de solicitud de permiso - idle_prompt - Notificación de estado inactivo - auth_success - Autenticación exitosa - elicitation_dialog - El servidor MCP abrió un formulario de excitación - elicitation_url_dialog - El servidor MCP le pide que abra una URL del navegador - elicitation_complete - Provocación de modo URL reportada completa - elicitation_response Respuesta de eliminación enviada al servidor - agent_needs_input - El agente de fondo necesita información (v2.1.198) agent_completed - Agente de fondo terminado (v.2.198) quota_auto_resume_fired / quota_auto_resume_stale / quota_auto_resume_disabled - Límite de uso de los resultados de auto-resumen (v2.1.234) - plugin_notification - Un mod planteó una notificación con $.ui.notify; el hook title es el nombre del mod a menos que el conjunto de mod uno
PreModelSwitch
Corre antes Claude Code aplica un interruptor de modelo solicitado, por ejemplo cuando se ejecuta /model, o cuando un componente pide un modelo diferente. Requiere v2.1.251 o posterior.
Coincidencias: el nombre canónico del modelo que se está cambiando, derivado de to_modelCoincidir con un modelo exacto (claude-opus-5) o una familia con un regex (.*opus.*).
Campos de entrada: además de los campos comunes, el hook recibe from_model (el modelo en uso antes del interruptor) y to_model (el modelo solicitado).
Puede bloquear: Sí. Código de salida 2 bloquea el interruptor y muestra a stderr como un error, por lo que la sesión mantiene su modelo actual. Utilice esto para cambiar el modelo de gate o veto, por ejemplo, para mantener un proyecto sensible a los costos fuera del modelo más caro.
Tiempo de espera: este evento baja la command, http, y mcp_tool tiempo de espera predeterminado a 30 segundos.
Configuración:
{
"hooks": {
"PreModelSwitch": [
{
"matcher": ".*opus.*",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/gate-model-switch.sh"
}
]
}
]
}
}
#!/bin/bash
# gate-model-switch.sh - refuse a switch to Opus on this project
input=$(cat)
to_model=$(echo "$input" | jq -r '.to_model')
if [[ "$to_model" == *opus* ]]; then
echo "This project is budgeted for Sonnet; staying on the current model." >&2
exit 2
fi
exit 0
PostModelSwitch
Corre después El modelo de la sesión ha cambiado. También se activa para los cambios que Claude Code se hace a sí mismo, como restaurar el modelo previamente seleccionado cuando se reanuda una sesión, no sólo para los interruptores que se solicitan. Requiere v2.1.251 o posterior.
Coincidencias: igual que PreModelSwitch — el nombre canónico derivado de: to_model.
Campos de entrada: from_model y to_model, junto a los campos comunes.
Puede bloquear: No. El interruptor ya ha ocurrido; el hook sólo puede observar y reaccionar.
Tiempo de espera: este evento baja la command, http, y mcp_tool tiempo de espera predeterminado a 30 segundos.
Configuración:
{
"hooks": {
"PostModelSwitch": [
{
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/log-model-switch.sh"
}
]
}
]
}
}
#!/bin/bash
# log-model-switch.sh - append every model change to a session log
input=$(cat)
from=$(echo "$input" | jq -r '.from_model')
to=$(echo "$input" | jq -r '.to_model')
echo "$(date -Iseconds) $from -> $to" >> ~/.claude/model-switches.log
exit 0
Hooks con alcance de componente
Los hooks se pueden unir a componentes específicos (skills, agentes, comandos) en su materia frontal:
En SKILL.md, agent.md o command.md:
---
name: secure-operations
description: Perform operations with security checks
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/check.sh"
once: true # Skill frontmatter only: removed after first successful run
---
Acontecimientos apoyados: todos los eventos de hook. Los hooks de subagente solo se ejecutan mientras el subagente está activo (Stop → SubagentStop); los hooks de habilidad se registran cuando la habilidad es invocada y permanecen activos durante el resto de la sesión. Desde v2.1.304, las skills de una fuente (su proyecto, su carpeta de skills personales, o un plugin) pueden registrarse como máximo 1.024 hooks y 16.384 caracteres de coincidencia por sesión; una habilidad más allá del límite no registra ninguno de sus hooks (busque en el claude --debug log for registers none of its hooks).
Esto permite definir hooks directamente en el componente que los utiliza, manteniendo juntos el código relacionado.
Hooks en el subagente Frontmatter
Cuándo un Stop el hook se define en la materia frontal de un subagente, se convierte automáticamente en una SubagentStop enganchado a ese subagente. Esto asegura que el hook de parada sólo se enciende cuando ese subagente específico se completa, en lugar de cuando la sesión principal se detiene.
---
name: code-review-agent
description: Automated code review subagent
hooks:
Stop:
- hooks:
- type: prompt
prompt: "Verify the code review is thorough and complete."
# The above Stop hook auto-converts to SubagentStop for this subagent
---
Se requiere confianza en el espacio de trabajo (v2.1.218): Hooks de la materia frontal en un proyecto ahora requiere la aceptación de confianza del espacio de trabajo para la carpeta que el archivo de agente vino de antes de que se ejecuten. Antes de v2.1.218, estos hooks podrían ejecutarse desde carpetas en las que no habías confiado. documentación de subagentes para los cuales están exentos los alcances.
Evento de solicitud de permiso
Maneja las solicitudes de permisos con formato de salida personalizado:
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow|deny",
"updatedInput": {},
"message": "Custom message",
"interrupt": false
}
}
}
Entrada y salida de hook
JSON Input (a través de stdin)
Todos los hooks reciben la entrada JSON a través de stdin:
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/working/directory",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.js",
"content": "..."
},
"tool_use_id": "toolu_01ABC123...",
"agent_id": "agent-abc123",
"agent_type": "security-reviewer",
"worktree": "/path/to/worktree",
"effort": { "level": "medium" }
}
Campos comunes:
| Campo | Descripción |
|---|---|
session_id |
Identificador único de la sesión |
transcript_path |
Ruta al archivo de transcripción de la conversación |
cwd |
Directorio de trabajo actual |
prompt_id |
UUID del prompt que se está procesando; se correlaciona con la OpenTelemetry prompt.id atributo (v2.1.196) |
hook_event_name |
Nombre del evento que desencadenó el hook |
agent_id |
Identificador del agente que ejecuta este hook |
agent_type |
Nombre del agente (por ejemplo, "Explore" o "security-reviewer"); presente sólo cuando el período de sesiones utiliza --agent o los fuegos de hook dentro de un subagente |
worktree |
Ruta hacia el árbol de trabajo git, si el agente está corriendo en uno |
effort.level |
(v2.1.133+) Nivel de esfuerzo activo: low, medium, high, xhigho bien max |
Códigos de salida
| Código de salida | Significado | Comportamiento |
|---|---|---|
| 0 | Éxito | Continuar, analizar JSON stdout |
| 2 | Error de bloqueo | Operación de bloque, stderr mostrado como error |
| Otros | Error no de bloqueo | Continuar;a <hook name> hook error aviso en la transcripción muestra la primera línea de stderr |
Salida JSON (stdout, código de salida 0)
{
"continue": true,
"stopReason": "Optional message if stopping",
"suppressOutput": false,
"systemMessage": "Optional warning message",
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "File is in allowed directory",
"updatedInput": {
"file_path": "/modified/path.js"
}
}
}
Ámbito de aplicación (v2.1.121+):
hookSpecificOutput.updatedToolOutputahora es honrado por todos herramientas, no sólo herramientas MCP.PostToolUsehook enBash,Edit,Read, etc. puede reescribir la salida de la herramienta antes de que Claude la vea — útil para redaccionar secretos, normalizar diffs o filtrar salidas de comandos ruidosas. El valor debe coincidir con la forma de salida de la herramienta —Bashdevuelve un objeto constdout,stderr,interrupted, yisImage; para las herramientas integradas se ignora un valor que no coincide y se utiliza la salida original. Ejemplo (describa los códigos de color ANSI de unaBashsalida):
json { "hookSpecificOutput": { "hookEventName": "PostToolUse", "updatedToolOutput": { "stdout": "<output with ANSI escapes removed>", "stderr": "", "interrupted": false, "isImage": false } } }
retry(PermissionDenied): Use JSONhookSpecificOutput.retry: truepara decirle al modelo que puede volver a intentar la llamada de la herramienta denegada.Depreciados
PreToolUseFormulario de decisiónA favorPreToolUse, el nivel superiordecisionyreasoncampos son Depreciados — UsohookSpecificOutput.permissionDecision(allow/deny/ask/defer) ypermissionDecisionReasonEn cambio, la precedencia entre las decisiones esdeny>defer>ask>allow. Tenga en cuenta también quesuppressOutputes aceptado pero no tiene efecto.
terminalSequence (v2.1.141)
Los hooks pueden emitir secuencias de escape de OSC (orden del sistema operativo) al configurar terminalSequence en la salida JSON. El host escribe la secuencia en su terminal de control cuando el hook regresa — útil para notificaciones de escritorio, actualizaciones de título de ventana y campanas de terminal sin requerir un TTY propio.
| Campo | Tipo | Descripción |
|---|---|---|
terminalSequence |
cadena | Secuencia de escape en bruto (típicamente OSC 9 / OSC 0 / OSC 777). |
Ejemplo: disparar una notificación de escritorio OSC 9 cuando se complete una tarea larga. Escribir ESC y BEL como JSON escapa (\u001b, \u0007o construir el valor con printf y jq; los bytes de control en bruto dentro de una cadena JSON fallan al analizar:
{
"terminalSequence": "\u001b]9;Task complete\u0007"
}
Configurelo en una Stop El soporte de secuencia depende de la terminal; iTerm2, Windows Terminal, y WezTerm honor OSC 9, mientras que Kitty utiliza OSC 99.
Variables de entorno
| Variable | Disponibilidad | Descripción |
|---|---|---|
CLAUDE_PROJECT_DIR |
Todos los hooks | Ruta absoluta a la raíz del proyecto |
CLAUDE_ENV_FILE |
SessionStart, CwdChanged, FileChanged | Ruta de archivo para env vars persistentes |
CLAUDE_CODE_REMOTE |
Todos los hooks | "true" si se ejecuta en entornos remotos |
${CLAUDE_PLUGIN_ROOT} |
Hooks de complementos | Ruta al directorio del complemento |
${CLAUDE_PLUGIN_DATA} |
Hooks de complementos | Ruta al directorio de datos del complemento |
CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS |
Hooks de fin de sesión | Tiempo de espera configurable en milisegundos para los hooks SessionEnd (sobrepasa por defecto) |
CLAUDE_CODE_SESSION_ID |
Subprocesos de herramientas Bash (v2.1.132+) | Sesión UUID; coincide con el session_id campo en entrada de hook JSON. Utilice para correlacionar registros de bash con telemetría de hook. |
CLAUDE_EFFORT |
Subprocesos de herramientas Bash (v2.1.133+) | Nivel de esfuerzo activo (low/medium/high/xhigh/max); coincidencias effort.level en la entrada de hook JSON. |
CLAUDE_CODE_STOP_HOOK_BLOCK_CAP |
A lo largo de todo el proceso (v2.1.143+) | Máximo de bloqueos consecutivos de parada antes de que Claude Code anule el siguiente bloque y termine la vuelta (por defecto 8). Establecer a 0 para desactivar la tapa. |
Garfios rápidos
Por Stop y SubagentStop eventos, puede utilizar la evaluación basada en LLM:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Review if all tasks are complete. Return your decision.",
"timeout": 30
}
]
}
]
}
}
Esquema de respuesta LLM:
{
"ok": false,
"reason": "Tests were not run after the last edit"
}
ok: true permite la acción; reason se requiere cuando ok es false.
Ejemplos
Ejemplo 1: Validador de órdenes Bash (PreToolUse)
Archivo: .claude/hooks/validate-bash.py
#!/usr/bin/env python3
import json
import sys
import re
BLOCKED_PATTERNS = [
(r"\brm\s+-rf\s+/", "Blocking dangerous rm -rf / command"),
(r"\bsudo\s+rm", "Blocking sudo rm command"),
]
def main():
input_data = json.load(sys.stdin)
tool_name = input_data.get("tool_name", "")
if tool_name != "Bash":
sys.exit(0)
command = input_data.get("tool_input", {}).get("command", "")
for pattern, message in BLOCKED_PATTERNS:
if re.search(pattern, command):
print(message, file=sys.stderr)
sys.exit(2) # Exit 2 = blocking error
sys.exit(0)
if __name__ == "__main__":
main()
Configuración:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py\""
}
]
}
]
}
}
Ejemplo 2: Escáner de seguridad (PostToolUse)
Archivo: .claude/hooks/security-scan.py
#!/usr/bin/env python3
import json
import sys
import re
SECRET_PATTERNS = [
(r"password\s*=\s*['\"][^'\"]+['\"]", "Potential hardcoded password"),
(r"api[_-]?key\s*=\s*['\"][^'\"]+['\"]", "Potential hardcoded API key"),
]
def main():
input_data = json.load(sys.stdin)
tool_name = input_data.get("tool_name", "")
if tool_name not in ["Write", "Edit"]:
sys.exit(0)
tool_input = input_data.get("tool_input", {})
content = tool_input.get("content", "") or tool_input.get("new_string", "")
file_path = tool_input.get("file_path", "")
warnings = []
for pattern, message in SECRET_PATTERNS:
if re.search(pattern, content, re.IGNORECASE):
warnings.append(message)
if warnings:
output = {
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": f"Security warnings for {file_path}: " + "; ".join(warnings)
}
}
print(json.dumps(output))
sys.exit(0)
if __name__ == "__main__":
main()
Ejemplo 3: Código de formato automático (PostToolUse)
Archivo: .claude/hooks/format-code.sh
#!/bin/bash
# Read JSON from stdin
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_name', ''))")
FILE_PATH=$(echo "$INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_input', {}).get('file_path', ''))")
if [ "$TOOL_NAME" != "Write" ] && [ "$TOOL_NAME" != "Edit" ]; then
exit 0
fi
# Format based on file extension
case "$FILE_PATH" in
*.js|*.jsx|*.ts|*.tsx|*.json)
command -v prettier &>/dev/null && prettier --write "$FILE_PATH" 2>/dev/null
;;
*.py)
command -v black &>/dev/null && black "$FILE_PATH" 2>/dev/null
;;
*.go)
command -v gofmt &>/dev/null && gofmt -w "$FILE_PATH" 2>/dev/null
;;
esac
exit 0
Ejemplo 4: Validador rápido (UserPromptSubmit)
Archivo: .claude/hooks/validate-prompt.py
#!/usr/bin/env python3
import json
import sys
import re
BLOCKED_PATTERNS = [
(r"delete\s+(all\s+)?database", "Dangerous: database deletion"),
(r"rm\s+-rf\s+/", "Dangerous: root deletion"),
]
def main():
input_data = json.load(sys.stdin)
prompt = input_data.get("prompt", "")
for pattern, message in BLOCKED_PATTERNS:
if re.search(pattern, prompt, re.IGNORECASE):
output = {
"decision": "block",
"reason": f"Blocked: {message}"
}
print(json.dumps(output))
sys.exit(0)
sys.exit(0)
if __name__ == "__main__":
main()
Ejemplo 5: Hook de parada inteligente (basado en el impulso)
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Review if Claude completed all requested tasks. Check: 1) Were all files created/modified? 2) Were there unresolved errors? If incomplete, explain what's missing.",
"timeout": 30
}
]
}
]
}
}
Ejemplo 6: Seguimiento de uso del contexto (pares de hooks)
Consumo de tokens de seguimiento por solicitud utilizando UserPromptSubmit (pre-mensaje) y Stop (post-respuesta) hooks juntos.
Archivo: .claude/hooks/context-tracker.py
#!/usr/bin/env python3
"""
Context Usage Tracker - Tracks token consumption per request.
Uses UserPromptSubmit as "pre-message" hook and Stop as "post-response" hook
to calculate the delta in token usage for each request.
Token Counting Methods:
1. Character estimation (default): ~4 chars per token, no dependencies
2. tiktoken (optional): More accurate (~90-95%), requires: pip install tiktoken
"""
import json
import os
import sys
import tempfile
# Configuration
CONTEXT_LIMIT = 1000000 # 1M on Opus 5, Sonnet 5, Opus 4.8, Sonnet 4.6; 200000 on Haiku 4.5 (adjust for your model)
USE_TIKTOKEN = False # Set True if tiktoken is installed for better accuracy
def get_state_file(session_id: str) -> str:
"""Get temp file path for storing pre-message token count, isolated by session."""
return os.path.join(tempfile.gettempdir(), f"claude-context-{session_id}.json")
def count_tokens(text: str) -> int:
"""
Count tokens in text.
Uses tiktoken with p50k_base encoding if available (~90-95% accuracy),
otherwise falls back to character estimation (~80-90% accuracy).
"""
if USE_TIKTOKEN:
try:
import tiktoken
enc = tiktoken.get_encoding("p50k_base")
return len(enc.encode(text))
except ImportError:
pass # Fall back to estimation
# Character-based estimation: ~4 characters per token for English
return len(text) // 4
def read_transcript(transcript_path: str) -> str:
"""Read and concatenate all content from transcript file."""
if not transcript_path or not os.path.exists(transcript_path):
return ""
content = []
with open(transcript_path, "r") as f:
for line in f:
try:
entry = json.loads(line.strip())
# Extract text content from various message formats
if "message" in entry:
msg = entry["message"]
if isinstance(msg.get("content"), str):
content.append(msg["content"])
elif isinstance(msg.get("content"), list):
for block in msg["content"]:
if isinstance(block, dict) and block.get("type") == "text":
content.append(block.get("text", ""))
except json.JSONDecodeError:
continue
return "\n".join(content)
def handle_user_prompt_submit(data: dict) -> None:
"""Pre-message hook: Save current token count before request."""
session_id = data.get("session_id", "unknown")
transcript_path = data.get("transcript_path", "")
transcript_content = read_transcript(transcript_path)
current_tokens = count_tokens(transcript_content)
# Save to temp file for later comparison
state_file = get_state_file(session_id)
with open(state_file, "w") as f:
json.dump({"pre_tokens": current_tokens}, f)
def handle_stop(data: dict) -> None:
"""Post-response hook: Calculate and report token delta."""
session_id = data.get("session_id", "unknown")
transcript_path = data.get("transcript_path", "")
transcript_content = read_transcript(transcript_path)
current_tokens = count_tokens(transcript_content)
# Load pre-message count
state_file = get_state_file(session_id)
pre_tokens = 0
if os.path.exists(state_file):
try:
with open(state_file, "r") as f:
state = json.load(f)
pre_tokens = state.get("pre_tokens", 0)
except (json.JSONDecodeError, IOError):
pass
# Calculate delta
delta_tokens = current_tokens - pre_tokens
remaining = CONTEXT_LIMIT - current_tokens
percentage = (current_tokens / CONTEXT_LIMIT) * 100
# Report usage
method = "tiktoken" if USE_TIKTOKEN else "estimated"
print(f"Context ({method}): ~{current_tokens:,} tokens ({percentage:.1f}% used, ~{remaining:,} remaining)", file=sys.stderr)
if delta_tokens > 0:
print(f"This request: ~{delta_tokens:,} tokens", file=sys.stderr)
def main():
data = json.load(sys.stdin)
event = data.get("hook_event_name", "")
if event == "UserPromptSubmit":
handle_user_prompt_submit(data)
elif event == "Stop":
handle_stop(data)
sys.exit(0)
if __name__ == "__main__":
main()
Configuración:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/context-tracker.py\""
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/context-tracker.py\""
}
]
}
]
}
}
Cómo funciona:
1. UserPromptSubmit fuegos antes de que su prompt se procese - guarda el recuento actual de tokens 2. Stop fuegos después de Claude responde - calcula delta e informa el uso 3. Cada sesión se aísla a través session_id en el nombre de archivo temporal
Métodos de recuento de tokens:
| Método | Exactitud | Dependencias | Velocidad |
|---|---|---|---|
| Estimación de caracteres | ~80-90% | Ninguna | <1ms |
| tiktoken (p50k_base) | ~90-95% | pip install tiktoken |
<10ms |
Nota: Anthropic no ha publicado un tokenizer offline oficial. Ambos métodos son aproximaciones.La transcripción incluye indicaciones de usuario, respuestas de Claude y salidas de herramientas; desde v2.1.300 también registra el sistema de solicitud de cada API, herramientas y parámetros.
Ejemplo 7: Permisos de automodo de semillas (Script de configuración única)
Un guión de configuración de una sola vez que se siembra ~/.claude/settings.json con reglas de permiso seguras equivalentes a la línea de base de modo automático de Claude Code — 37 por defecto, hasta 63 con cada bandera de categoría habilitada — sin ningún hook, sin recordar futuras opciones. Ejecutar una vez; seguro para volver a ejecutar (normas de saltos ya presentes).
Archivo: 09-advanced-features/setup-auto-mode-permissions.py
# Preview what would be added
python3 09-advanced-features/setup-auto-mode-permissions.py --dry-run
# Apply
python3 09-advanced-features/setup-auto-mode-permissions.py
Lo que se añade:
| Categoría | Ejemplos |
|---|---|
| Herramientas integradas | Read(*), Grep(*), Agent(*), WebSearch(*) |
| Ediciones opcionales (--include-edits) | Edit(*) |
| Git lea | Bash(git status:*), Bash(git log:*), Bash(git diff:*) |
| Git write (local) | Bash(git add:*), Bash(git commit:*), Bash(git checkout:*) |
| Gestores de paquetes | Bash(npm ci:*), Bash(npm install:*), Bash(pip install:*), Bash(pip3 install:*) |
| Construir & prueba | Bash(make:*), Bash(pytest:*), Bash(go test:*) |
| Concha común | Bash(ls:*), Bash(cat:*), Bash(find:*) |
| GitHub CLI | Bash(gh pr view:*), Bash(gh pr create:*), Bash(gh issue list:*) |
Lo que se excluye intencionalmente (nunca añadido por este guión): - rm -rf, sudo, empuje de la fuerza, git reset --hard
- DROP TABLE, kubectl delete, terraform destroy
- npm publish, curl | bash, despliegues de producción
Ejemplo 8: Registrador de progreso del aprendizaje (SessionEnd)
Registra los módulos que estudiaste al final de cada sesión de Claude Code. El progreso se almacena en ~/.claude-howto-progress.json — fuera de la repo, por lo que sobrevive git pull sin ser sobrescrito.
Por qué SessionEnd y no Stop?
Stop incendios después de cada Claude responde. SessionEnd dispara una vez cuando la sesión termina, exactamente lo que quieres para un diario de fin de sesión.
¿Por qué no hay señal interactiva? Los hooks corren sin un terminal de control, así que /dev/tty no está disponible y un hook no le puede indicar. Pase los módulos (y notas opcionales) como variables de entorno cuando inicie Claude Code, por ejemplo. CLAUDE_HOWTO_MODULES=06,07 claude.
Archivo: 06-hooks/session-end.sh
#!/usr/bin/env bash
# SessionEnd hook: records the modules worked on, then appends a session record
# to ~/.claude-howto-progress.json for persistent learning progress tracking.
PROGRESS_FILE="$HOME/.claude-howto-progress.json"
# Guard: only run inside this repo
if [[ "$CLAUDE_PROJECT_DIR" != *"claude-howto"* ]] && [[ "$PWD" != *"claude-howto"* ]]; then
exit 0
fi
if [ ! -f "$PROGRESS_FILE" ]; then
echo '{"sessions":[]}' > "$PROGRESS_FILE"
fi
DATE=$(date +"%Y-%m-%d")
TIME=$(date +"%H:%M")
# Modules worked on, e.g. launch with: CLAUDE_HOWTO_MODULES=06,07 claude
# 01=Slash 02=Memory 03=Skills 04=Subagents 05=MCP
# 06=Hooks 07=Plugins 08=Checkpoints 09=Advanced 10=CLI
INPUT="${CLAUDE_HOWTO_MODULES:-}"
if [ -z "$INPUT" ] || [ "$INPUT" = "skip" ]; then
exit 0
fi
MODULES_JSON=$(echo "$INPUT" | tr ',' '\n' | tr -d ' ' | while read -r m; do
case "$m" in
01) echo '"01-slash-commands"' ;;
02) echo '"02-memory"' ;;
03) echo '"03-skills"' ;;
04) echo '"04-subagents"' ;;
05) echo '"05-mcp"' ;;
06) echo '"06-hooks"' ;;
07) echo '"07-plugins"' ;;
08) echo '"08-checkpoints"' ;;
09) echo '"09-advanced-features"' ;;
10) echo '"10-cli"' ;;
*) echo "\"$m\"" ;;
esac
done | paste -sd ',' -)
# Optional notes, e.g. CLAUDE_HOWTO_NOTES="tried the pre-commit example"
NOTES="${CLAUDE_HOWTO_NOTES:-}"
# Pass NOTES as a separate argument so Python handles JSON escaping —
# avoids broken JSON when notes contain quotes or backslashes.
python3 - "$PROGRESS_FILE" "$DATE" "$TIME" "$MODULES_JSON" "$NOTES" <<'PYEOF'
import sys, json
path, date, time_str, modules_raw, notes = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]
new_session = {
"date": date,
"time": time_str,
"modules": json.loads(f"[{modules_raw}]") if modules_raw else [],
"notes": notes,
}
with open(path, 'r') as f:
data = json.load(f)
data.setdefault('sessions', []).append(new_session)
with open(path, 'w') as f:
json.dump(data, f, indent=2)
PYEOF
echo " Saved to $PROGRESS_FILE"
Instalar — copiar el script en el directorio de hook del proyecto para que la ruta en settings.json resuelve:
mkdir -p .claude/hooks
cp 06-hooks/session-end.sh .claude/hooks/
chmod +x .claude/hooks/session-end.sh
Configuración (en .claude/settings.json):
{
"hooks": {
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-end.sh\"",
"timeout": 10
}
]
}
]
}
}
Los hooks SessionEnd tienen un presupuesto predeterminado de 1,5 segundos. "timeout" (en segundos) aumenta el presupuesto para igualar, hasta 60 segundos; un hook sin su propio timeout se cancela después de 1,5 segundos.
Producto ~/.claude-howto-progress.json:
{
"sessions": [
{
"date": "2026-04-18",
"time": "14:32",
"modules": ["06-hooks", "07-plugins"],
"notes": "Installed first hook, tried pre-commit example"
}
]
}
Patrones clave demostrados:
| Patrón | ¿Por qué importa? |
|---|---|
SessionEnd evento |
Incendios una vez en la salida — no después de cada respuesta como Stop |
INPUT="${CLAUDE_HOWTO_MODULES:-}" |
Los hooks no tienen terminal (/dev/tty no está disponible), por lo que la entrada viene de env vars |
"timeout": 10 |
SessionEnd los hooks se cancelan después de 1.5 s a menos que establezcan su propio timeout |
$CLAUDE_PROJECT_DIR |
Ruta portátil — nunca codificar /Users/yourname/... |
| Cláusula de protección superior | Evita que el hook se ejecute en proyectos no relacionados si se instala globalmente |
| Conservar fuera de la repo | ~/ El camino sobrevive git pull sin sobrescribir sus datos |
Compañero: rastreador visual de progreso
Para una interfaz de usuario completa basada en casillas que cubra los 10 módulos, abra el rastreador incluido en su navegador:
open local-progress/index.html
El progreso se almacena en el navegador localStorage (nunca escrito en el disco dentro de la repo). Exportar para guardar una instantánea como JSON, y Importar para restaurarlo.
Hooks de complemento
Plugins pueden incluir hooks en su hooks/hooks.json archivo:
Archivo: plugins/hooks/hooks.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}\"/scripts/validate.sh"
}
]
}
]
}
}
Variables de entorno en hooks de complemento:
- ${CLAUDE_PLUGIN_ROOT} - Ruta al directorio del complemento - ${CLAUDE_PLUGIN_DATA} - Ruta al directorio de datos del complemento
Esto permite que los plugins incluyan hooks de validación y automatización personalizados.
Hooks de herramientas MCP
Herramientas MCP siguen el patrón mcp__<server>__<tool>:
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__memory__.*",
"hooks": [
{
"type": "command",
"command": "echo '{\"systemMessage\": \"Memory operation logged\"}'"
}
]
}
]
}
}
Consideraciones de seguridad
Descargo de responsabilidad
USO EN SU PROPIO RIESGO: Los hooks ejecutan comandos de shell arbitrarios. Usted es el único responsable de: - Comandos que configura - Permisos de acceso/modificación de archivos - Posible pérdida de datos o daño al sistema - Pruebas de hooks en entornos seguros antes de utilizar la producción
Notas de seguridad
- Se requiere confianza en el espacio de trabajo: Los
statusLineyfileSuggestionLos comandos de salida de hook ahora requieren aceptación de confianza en el espacio de trabajo antes de que entren en vigor. - Tamaño del terminal de la línea de estado (v2.1.153): Los scripts de órdenes de línea de estado ahora reciben
COLUMNSyLINESvariables de entorno, para que un script pueda adaptar su salida a la anchura/altura del terminal (por ejemplo.[ "$COLUMNS" -lt 80 ] && short_output). - Hooks HTTP y variables de entorno: Los hooks HTTP requieren un explícito
allowedEnvVarslista para utilizar la interpolación variable entorno en los valores de cabecera. Esto evita fuga accidental de variables de entorno sensibles a puntos finales remotos. - Jerarquía de configuraciones administradas: Los
disableAllHooksla configuración ahora respeta la jerarquía de ajustes gestionados, lo que significa que la configuración a nivel de organización puede imponer la inhabilitación de hook que los usuarios individuales no pueden anular. - Autoaprobación de PowerShell (v2.1.119): Los comandos de herramientas PowerShell pueden ser aprobados automáticamente en modo de permiso, coincidiendo con Bash. Esto trae paridad para los usuarios de Windows que ejecutan Claude Code con las herramientas de shell respaldadas por PowerShell.
- Bash env-var desnudo auto-aprobado cerrado (v2.1.145): Antes de v2.1.145, un comando Bash del formulario
FOO=bar somecommand(una asignación de variable desnuda en línea con un comando no-allowlisted) podría ser aprobada automáticamente cuando sóloFOO=barpor sí mismo estaba en la lista de permisos. v2.1.145 cierra esto — tales comandos ahora pulsan el prompt de permisos. Scripts que confían en el permiso implícito comenzará a solicitar; volver a permitir explícitamente a través de unBash(...)regla de permisos que cubre el comando completo, no sólo la asignación de variables.
Mejores prácticas
| Hacer | No lo hagas. |
|---|---|
| Validar y sanear todas las entradas | Datos de entrada de confianza a ciegas |
Variables de la shell de la cita: "$VAR" |
Usar sin citar: $VAR |
Vía de bloqueo transversal (..) |
Permitir rutas arbitrarias |
Utilice rutas absolutas con $CLAUDE_PROJECT_DIR |
Rutas de código duro |
Saltar archivos sensibles (.env, .git/, llaves) |
Procesar todos los archivos |
| Primero hooks de ensayo aislados | Implementar hooks no probados |
Usar explícito allowedEnvVars para hooks HTTP |
Exponga todas las env vars a los webhooks |
Depuración
Habilitar el modo de depuración
Ejecutar Claude con la bandera de depuración para registros de hook detallados:
claude --debug
Modo Verbose
Uso Ctrl+O en Claude Code para activar el modo verbose y ver el progreso de la ejecución de hook.
Hooks de prueba independientemente
# Test with sample JSON input
echo '{"tool_name": "Bash", "tool_input": {"command": "ls -la"}}' | python3 .claude/hooks/validate-bash.py
# Check exit code
echo $?
Ejemplo de configuración completo
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py\"",
"timeout": 10
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/format-code.sh\"",
"timeout": 30
},
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/security-scan.py\"",
"timeout": 10
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-prompt.py\""
}
]
}
],
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/session-init.sh\""
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Verify all tasks are complete before stopping.",
"timeout": 30
}
]
}
]
}
}
Detalles de ejecución de hook
| Aspecto | Comportamiento |
|---|---|
| Tiempo muerto | 600 segundos predeterminado para comando/http/mcp_tool (30 para prompt, 60 para agente); configurable por hook |
| Paralelización | Todos los hooks coincidentes se ejecutan en paralelo; desde v2.1.302, como máximo 12 hooks de prompt o agente de un evento se ejecutan a la vez y el resto espera su turno. |
| Deduplicación | Comandos de hook idénticos deduplicados |
| Medio ambiente | Se ejecuta en el directorio actual con el entorno de Claude Code |
Solución de problemas
Hook no ejecuta
- Verifique que la sintaxis de configuración de JSON sea correcta
- Comprobar el patrón de emparejador coincide con el nombre de la herramienta
- Asegúrese de que el script existe y es ejecutable:
chmod +x script.sh - Corre
claude --debugver registros de ejecución de hooks - Verificar hook lee JSON de stdin (no órdenes args)
Bloques de hook inesperadamente
- Hook de ensayo con muestra JSON:
echo '{"tool_name": "Write", ...}' | ./hook.py - Código de salida de comprobación: debe ser 0 para permitir, 2 para bloque
- Compruebe la salida stderr (mostrada en el código de salida 2)
Errores de análisis JSON
- Siempre lea de stdin, no argumentos de comandos
- Usar el análisis JSON adecuado (no la manipulación de cadenas)
- Manejar los campos que faltan con gracia
Instalación
Paso 1: Crear directorio de hooks
mkdir -p ~/.claude/hooks
Paso 2: Copiar hooks de ejemplo
cp 06-hooks/*.sh ~/.claude/hooks/
chmod +x ~/.claude/hooks/*.sh
Paso 3: Configurar en la configuración
Editar ~/.claude/settings.json o .claude/settings.json con la configuración de hook que se muestra arriba.
Conceptos relacionados
- Puntos de control y rebobinado - Guardar y restaurar el estado de conversación
- Comandos de barra - Crear comandos personalizados de barra
- Skills - Capacidades autónomas reutilizables
- Subagentes - Ejecución de tareas delegadas
- Plugins - Paquetes de extensión agrupados
- Funciones avanzadas - Explorar capacidades avanzadas de Claude Code
Recursos adicionales
- Documentación de hooks oficiales - Referencia de hooks completos
- Referencia de la CLI - Documentación de interfaz de línea de comandos
- Guía de memoria - Configuración persistente del contexto
Última actualización: 26 de octubre de 2026 Versión de Claude Code: 2.1.310 Fuentes: - https://code.claude.com/docs/en/hooks - https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md - https://code.claude.com/docs/en/sub-agents - https://code.claude.com/docs/en/permissions - https://code.claude.com/docs/en/tools-reference#task-tool-availability - https://code.claude.com/docs/en/plugins/manifest-reference#quoting-and-path-separators - https://github.com/anthropics/claude-code/releases/tag/v2.1.295 - https://code.claude.com/docs/en/hooks#pretooluse - https://code.claude.com/docs/en/hooks#pretooluse-decision-control - https://code.claude.com/docs/en/hooks#hook-lifecycle - https://code.claude.com/docs/en/hooks#userpromptsubmit-input - https://code.claude.com/docs/en/changelog#2-1-299 - https://code.claude.com/docs/en/changelog#2-1-300 - https://code.claude.com/docs/en/changelog#2-1-301 - https://code.claude.com/docs/en/changelog#2-1-302 - https://code.claude.com/docs/en/hooks#defer-a-tool-call-for-later - https://code.claude.com/docs/en/hooks-guide#limitations - https://code.claude.com/docs/en/hooks#notification - https://code.claude.com/docs/en/changelog#2-1-304 - https://code.claude.com/docs/en/hooks#limits-on-skill-hooks - https://code.claude.com/docs/en/changelog#2-1-309 - https://code.claude.com/docs/en/hooks#failed-hooks-on-windows-without-git-bash Modelos compatibles: Claude Fable 5, Claude Opus 5, Claude Sonnet 5, Claude Sonnet 4.6, Claude Opus 4.8, Claude Haiku 4.5