C Claude Code Internals
EN | ES

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.

27 eventos de hook 4 tipos de hook 7 fuentes de hooks todos en paralelo
i Automatización basada en eventos para Claude Code
Cada vez que Claude ejecuta una herramienta, inicia una sesión, solicita un permiso o completa un turno, los hooks se activan. Se ejecutan en paralelo, reciben un payload JSON por stdin y devuelven JSON estructurado por stdout. El código de salida 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ón
async — ejecutar en segundo plano sin bloquear
asyncRewake — segundo plano + despertar al modelo en exit 2

prompt — 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 completo
model — por defecto modelo pequeño y rápido
timeout — por defecto: 30 segundos

agent — verificación multi-turno

{
  "type": "agent",
  "prompt": "Verifica que los tests siguen pasando: $ARGUMENTS",
  "timeout": 60,
  "model": "claude-sonnet-4-6"
}
Tiene acceso a herramientas, hasta 50 turnos
model — por defecto Haiku
timeout — por defecto: 60 segundos

http — webhook / API externa

{
  "type": "http",
  "url": "https://api.example.com/hooks/claude",
  "headers": {
    "Authorization": "Bearer $API_TOKEN"
  },
  "allowedEnvVars": ["API_TOKEN"],
  "timeout": 30
}
Envía el JSON de entrada como cuerpo por POST
allowedEnvVars — lista blanca para expansión $VAR en headers
Protección SSRF: bloquea localhost/IPs internas

Formato 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

PreToolUse / PostToolUse
tool_name, tool_input, tool_use_id
// PostToolUse añade: tool_response
// PostToolUseFailure añade: error, is_interrupt
SessionStart
source, agent_type, model
PermissionRequest
tool_name, tool_input, permission_suggestions
FileChanged / CwdChanged
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

PreToolUse
permissionDecision: "allow|deny|ask"
updatedInput: { }      // modificar la entrada
additionalContext: ""  // inyectado como contexto
PostToolUse
additionalContext: ""
updatedMCPToolOutput: { }  // reemplazar salida MCP
SessionStart
additionalContext: ""
initialUserMessage: ""   // mensaje auto-inyectado
watchPaths: ["/ruta/"]   // configurar watchers
PermissionRequest
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ítica
disableAllHooks — en policySettings: deshabilita todos; en otras fuentes: solo los no gestionados

Elicitació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
! Los hooks se ejecutan con los permisos completos del usuario
Los hooks de comando se ejecutan como proceso de shell del usuario sin ningún sandboxing. Un hook malicioso en el .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.