Preludio

La mayoría de los desarrolladores pasan sus primeros meses con Claude Code en un modo reactivo. Aprobando llamadas a herramientas manualmente, revisando salidas después del hecho, y ocasionalmente detectando algo que no debería haber sucedido. Funciona. Pero es completamente reactivo.

El cambio llega cuando te das cuenta de que Claude Code tiene un sistema completo de eventos del ciclo de vida incorporado. Cada llamada a herramienta, cada inicio de sesión, cada edición de archivo dispara un evento. Y puedes engancharte a cualquiera de ellos con un comando de shell, un endpoint HTTP o un prompt de LLM que se ejecuta automáticamente.

Eso lo cambia todo sobre cómo trabajas con Claude Code. En lugar de revisar después del hecho, interceptas antes de la ejecución. En lugar de ejecutar linters manualmente, los activas automáticamente cuando Claude edita un archivo. En lugar de confiar en que se usaron los comandos correctos, registras cada llamada a herramienta en un endpoint central.

Esta no es una guía sobre un solo hook. Es una guía para pensar en Claude Code como un sistema basado en eventos y construir flujos de trabajo sobre él.

El problema

Claude Code toma cientos de decisiones durante una sesión típica. Qué archivos leer, qué comandos ejecutar, qué ediciones hacer, qué herramientas llamar. El modelo de interacción estándar te da un aviso de permisos para algunos de estos y aprobación automática para otros.

Ese modelo de permisos funciona para la seguridad. No funciona para la automatización. No puedes automatizar el linting haciendo clic en "aprobar" en cada edición. No puedes construir un registro de auditoría copiando manualmente las salidas de herramientas. No puedes hacer cumplir estándares de código esperando que Claude siga las instrucciones de tu CLAUDE.md.

Lo que necesitas es una forma de reaccionar a las acciones de Claude Code programáticamente. Ejecutar un linter cada vez que un archivo cambie. Bloquear ciertos patrones antes de que se ejecuten. Registrar cada comando en una base de datos. Enviar una notificación cuando una sesión supere un umbral de tiempo.

El sistema de hooks es esa capa programática.

El camino

Entendiendo el ciclo de vida

Cada sesión de Claude Code sigue un ciclo de vida. La sesión comienza, el usuario envía un prompt, Claude decide qué herramientas usar, las herramientas se ejecutan y finalmente la sesión termina. Los hooks se disparan en puntos específicos de este ciclo de vida.

El sistema de hooks define múltiples eventos del ciclo de vida, cada uno disparándose en un punto diferente de la sesión. Los más usados son estos.

SessionStart se dispara cuando una sesión comienza o se reanuda. Úsalo para inicializar el logging, verificar prerrequisitos o configurar el entorno de trabajo.

UserPromptSubmit se dispara cuando envías un prompt, antes de que Claude lo procese. Úsalo para validar, transformar o registrar las entradas del usuario.

PreToolUse se dispara antes de que cualquier llamada a herramienta se ejecute. Este es el evento más poderoso porque puede aprobar, denegar o modificar la llamada a herramienta. Si tu hook devuelve un permissionDecision de "deny", la llamada a herramienta se bloquea. Si devuelve "allow", la llamada a herramienta procede sin preguntar al usuario.

PostToolUse se dispara después de que una llamada a herramienta tenga éxito. Úsalo para linting, testing, logging o cualquier reacción a una acción completada.

PostToolUseFailure se dispara después de que una llamada a herramienta falle. Úsalo para registrar errores, activar alertas o ejecutar limpieza.

Stop se dispara cuando Claude termina su respuesta. Úsalo para resúmenes a nivel de sesión, notificaciones o comprobaciones finales.

ConfigChange se dispara cuando un archivo de configuración cambia durante una sesión. Esto es crítico para la seguridad porque detecta intentos de modificar ajustes durante la sesión.

SessionEnd se dispara cuando una sesión termina. Úsalo para limpieza, logging final o liberación de recursos.

Cada evento pasa contexto JSON a tu manejador de hook. Para PreToolUse, eso incluye el nombre de la herramienta y la entrada completa de la herramienta. Para PostToolUse, incluye también la salida de la herramienta. La referencia de hooks documenta el esquema completo para cada evento.

Prerrequisitos

Antes de escribir tu primer hook, asegúrate de tener lo siguiente preparado.

Claude Code 1.0.20 o posterior. Los hooks se introdujeron en Claude Code 1.0.20. Ejecuta claude --version para comprobarlo. Si estás en una versión anterior, actualiza con npm update -g @anthropic-ai/claude-code o a través de la instalación gestionada de tu organización.

jq para procesamiento de JSON. La mayoría de los scripts de hooks usan jq para analizar la entrada JSON que Claude Code pasa a los manejadores de hooks. Instálalo con brew install jq en macOS o sudo apt install jq en Debian y Ubuntu. Puedes verificar que está instalado ejecutando jq --version.

Permisos de ejecución en los scripts de hooks. Los scripts de shell deben estar marcados como ejecutables o fallarán silenciosamente. Después de crear cualquier script de hook, ejecuta chmod +x sobre él.

chmod +x .claude/hooks/block-rm.sh
chmod +x .claude/hooks/auto-lint.sh

Un directorio .claude/hooks/ en tu proyecto. Esto no es estrictamente necesario, ya que puedes colocar scripts de hooks en cualquier lugar, pero mantenerlos en .claude/hooks/ es la convención. Créalo con mkdir -p .claude/hooks.

Un archivo de configuración. Los registros de hooks van en .claude/settings.json para hooks a nivel de proyecto o ~/.claude/settings.json para hooks globales. Si el archivo aún no existe, créalo con un objeto JSON vacío {}.

Con esto preparado, estás listo para escribir hooks.

Tu primer hook. Bloquear comandos destructivos

El hook útil más simple es un manejador PreToolUse que bloquea comandos Bash peligrosos. Este hook comprueba cada comando Bash antes de que se ejecute y deniega cualquiera que contenga rm -rf.

Crea un archivo en .claude/hooks/block-rm.sh en tu proyecto.

#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "Destructive rm -rf command blocked by hook"
    }
  }'
else
  exit 0
fi

Regístralo en tu archivo de configuración. Puede vivir en .claude/settings.json para el proyecto o en ~/.claude/settings.json para todos tus proyectos.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}

El campo matcher es una regex que filtra qué herramienta activa el hook. "Bash" coincide solo con comandos Bash. "Edit|Write" coincidiría con modificaciones de archivos. Una cadena vacía o "*" coincide con todo. Puedes omitir el matcher completamente para coincidir con todas las ocurrencias.

Cuando Claude Code decide ejecutar rm -rf /tmp/build, el evento PreToolUse se dispara, el matcher comprueba Bash, el script del hook se ejecuta, encuentra rm -rf en el comando y devuelve una decisión de denegación. Claude ve la razón del rechazo y ajusta su enfoque. El comando nunca se ejecuta.

Auto-linting en cada cambio de archivo

El hook más comúnmente usado es un manejador PostToolUse que hace lint de los archivos cada vez que Claude los edita.

#!/bin/bash
# .claude/hooks/auto-lint.sh
TOOL_NAME=$(jq -r '.tool_name')
FILE_PATH=""

if [ "$TOOL_NAME" = "Edit" ] || [ "$TOOL_NAME" = "Write" ]; then
  FILE_PATH=$(jq -r '.tool_input.file_path // .tool_input.path // empty')
fi

if [ -z "$FILE_PATH" ]; then
  exit 0
fi

EXTENSION="${FILE_PATH##*.}"

case "$EXTENSION" in
  js|ts|jsx|tsx)
    npx eslint --fix "$FILE_PATH" 2>/dev/null
    ;;
  rs)
    cargo fmt -- "$FILE_PATH" 2>/dev/null
    ;;
  py)
    ruff format "$FILE_PATH" 2>/dev/null
    ;;
esac

exit 0

Regístralo con un matcher para operaciones Edit y Write.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/auto-lint.sh"
          }
        ]
      }
    ]
  }
}

Cada vez que Claude edita o crea un archivo, el linter se ejecuta inmediatamente. Sin paso manual. Sin terminal separado. El archivo se formatea antes de que Claude pase a su siguiente acción.

Hooks HTTP para logging centralizado

Los hooks de comandos se ejecutan localmente. Los hooks HTTP envían los datos del evento a un endpoint remoto. Aquí es donde los hooks se convierten en una herramienta empresarial.

Un hook HTTP envía la entrada JSON del evento como una solicitud POST a tu URL. El endpoint puede registrar los datos, ejecutar análisis o devolver una decisión a Claude Code usando el mismo formato JSON que los hooks de comandos.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "http",
            "url": "https://audit.yourcompany.com/claude-code/events",
            "timeout": 5000,
            "headers": {
              "Authorization": "Bearer $AUDIT_TOKEN",
              "X-Developer": "$USER"
            },
            "allowedEnvVars": ["AUDIT_TOKEN", "USER"]
          }
        ]
      }
    ]
  }
}

El campo headers soporta interpolación de variables de entorno, pero solo para variables listadas en allowedEnvVars. Esta es una medida de seguridad deliberada. Incluso si la configuración del hook referencia $AWS_SECRET_KEY, se resuelve como vacío a menos que esté explícitamente aprobado. Esto previene la fuga accidental de credenciales a través de configuraciones de hooks.

El matcher vacío significa que este hook se dispara en cada evento PostToolUse, independientemente de qué herramienta fue llamada. Cada lectura de archivo, cada comando Bash, cada edición se registra en tu endpoint central.

Tu endpoint recibe el payload completo del evento. Para un comando Bash, eso incluye la cadena del comando, la salida, el código de salida y el contexto de la sesión. Para un Edit, incluye la ruta del archivo, el contenido anterior, el contenido nuevo y el diff. Tienes todo lo que necesitas para construir un registro de auditoría completo.

Hooks de prompts. Interceptar la entrada del usuario

El evento UserPromptSubmit se dispara cada vez que envías un prompt a Claude Code, antes de que Claude comience a procesarlo. Esto te da la oportunidad de validar, transformar o registrar cada instrucción que entra al sistema.

Un caso de uso práctico es hacer cumplir convenciones de prompts en un equipo. Si tu organización requiere que ciertos proyectos siempre incluyan una referencia de ticket en los prompts, un hook UserPromptSubmit puede verificar eso.

#!/bin/bash
# .claude/hooks/require-ticket.sh
PROMPT=$(jq -r '.user_prompt // empty')

if [ -z "$PROMPT" ]; then
  exit 0
fi

# Comprobar si el prompt contiene una referencia de ticket como PROJ-123
if ! echo "$PROMPT" | grep -qE '[A-Z]+-[0-9]+'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "UserPromptSubmit",
      permissionDecision: "deny",
      permissionDecisionReason: "Please include a ticket reference (e.g. PROJ-123) in your prompt for audit tracking."
    }
  }'
fi

Regístralo para el evento UserPromptSubmit.

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/require-ticket.sh"
          }
        ]
      }
    ]
  }
}

Otro patrón útil es registrar cada prompt en un archivo local para revisión posterior. Esto es particularmente útil durante sesiones de pair programming o al incorporar nuevos miembros del equipo, ya que crea un registro de cómo el equipo interactúa con Claude Code.

#!/bin/bash
# .claude/hooks/log-prompts.sh
PROMPT=$(jq -r '.user_prompt // empty')
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

if [ -n "$PROMPT" ]; then
  echo "[$TIMESTAMP] $PROMPT" >> .claude/prompt-log.txt
fi

exit 0

Los hooks de prompts son especialmente potentes en ajustes gestionados empresariales donde los administradores pueden hacer cumplir políticas de prompts a nivel organizativo sin depender de que los desarrolladores individuales sigan las convenciones manualmente.

Tres recetas de hooks que vale la pena usar cada día

Receta 1. Auto-ejecutar tests después de comandos Bash que modifican archivos fuente.

Este hook PostToolUse vigila comandos Bash que contengan git commit y activa la suite de tests. Si los tests fallan, la salida se captura y se devuelve a Claude en la siguiente interacción.

#!/bin/bash
# .claude/hooks/post-commit-test.sh
COMMAND=$(jq -r '.tool_input.command // empty')

if echo "$COMMAND" | grep -q 'git commit'; then
  npm run test --silent 2>&1 | tail -20
fi

exit 0

Receta 2. Validar rutas de archivos antes de ediciones.

Este hook PreToolUse previene que Claude edite archivos fuera del directorio fuente del proyecto. Comprueba la ruta del archivo en las llamadas a herramientas Edit y Write y bloquea cualquier cosa fuera de src/, tests/ o docs/.

#!/bin/bash
# .claude/hooks/validate-paths.sh
FILE_PATH=$(jq -r '.tool_input.file_path // .tool_input.path // empty')

if [ -z "$FILE_PATH" ]; then
  exit 0
fi

ALLOWED_DIRS="src/ tests/ docs/ .claude/"

MATCHED=false
for DIR in $ALLOWED_DIRS; do
  if [[ "$FILE_PATH" == *"$DIR"* ]]; then
    MATCHED=true
    break
  fi
done

if [ "$MATCHED" = false ]; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "Edit restricted to src/, tests/, docs/, .claude/ directories"
    }
  }'
fi

Receta 3. Notificaciones de duración de sesión.

Este hook Stop comprueba cuánto tiempo lleva ejecutándose la sesión y envía una notificación si supera los 30 minutos. Útil para hacer seguimiento de sesiones de agente de larga duración.

#!/bin/bash
# .claude/hooks/session-timer.sh
SESSION_START=$(jq -r '.session.start_time // empty')

if [ -z "$SESSION_START" ]; then
  exit 0
fi

NOW=$(date +%s)
DURATION=$(( NOW - SESSION_START ))

if [ "$DURATION" -gt 1800 ]; then
  MINUTES=$(( DURATION / 60 ))
  notify-send "Claude Code" "Session running for ${MINUTES} minutes" 2>/dev/null || true
fi

exit 0

Patrones de orquestación multi-hook

Los proyectos reales rara vez necesitan un solo hook por evento. Claude Code soporta múltiples hooks en el mismo evento, y se ejecutan en el orden en que están listados en la configuración. Esto abre patrones de orquestación más potentes que cualquier hook individual.

Cadena de responsabilidad. Registra múltiples hooks PreToolUse en el mismo matcher, cada uno comprobando una condición diferente. El primer hook comprueba restricciones de ruta de archivo. El segundo comprueba políticas de horario. El tercero comprueba reglas de protección de ramas. Si cualquier hook devuelve una decisión de denegación, la llamada a herramienta se bloquea. Si todos pasan, la llamada a herramienta procede.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": ".claude/hooks/validate-paths.sh" },
          { "type": "command", "command": ".claude/hooks/check-business-hours.sh" },
          { "type": "command", "command": ".claude/hooks/branch-guard.sh" }
        ]
      }
    ]
  }
}

Cada hook es un script autocontenido que hace una cosa bien. Añadir o eliminar una comprobación es un cambio de una sola línea en la configuración. Sin scripts monolíticos que mantener.

Logging y linting en paralelo. Los hooks PostToolUse pueden combinar un paso de linting local con un paso de logging remoto. El linter se ejecuta como hook de comando y arregla el archivo localmente. El logger se ejecuta como hook HTTP y envía el evento a tu endpoint de auditoría. Ambos se disparan en el mismo evento, ambos se completan independientemente y ninguno bloquea al otro.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": ".claude/hooks/auto-lint.sh" },
          {
            "type": "http",
            "url": "https://audit.yourcompany.com/claude-code/edits",
            "timeout": 3000,
            "headers": { "Authorization": "Bearer $AUDIT_TOKEN" },
            "allowedEnvVars": ["AUDIT_TOKEN"]
          }
        ]
      }
    ]
  }
}

Fan-out de eventos. Usa múltiples matchers en el mismo evento para aplicar diferentes hooks a diferentes herramientas. Los comandos Bash reciben comprobaciones de seguridad. Las ediciones de archivos reciben linting. Todo se registra. Esto mantiene cada hook enfocado y evita lógica condicional compleja dentro de los scripts.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [{ "type": "command", "command": ".claude/hooks/block-rm.sh" }]
      },
      {
        "matcher": "Edit|Write",
        "hooks": [{ "type": "command", "command": ".claude/hooks/validate-paths.sh" }]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "",
        "hooks": [{
          "type": "http",
          "url": "https://audit.yourcompany.com/claude-code/events",
          "timeout": 3000
        }]
      }
    ]
  }
}

El principio clave: cada hook debería hacer una cosa. La orquestación sucede a través de la composición en la configuración, no a través de la complejidad dentro de los scripts.

Rendimiento de hooks y ajuste de timeouts

Los hooks se ejecutan de forma síncrona en el ciclo de vida de Claude Code. Un hook lento bloquea toda la sesión hasta que se completa o expira. Entender las características de rendimiento de tus hooks es esencial para mantener Claude Code responsivo.

Timeouts de hooks de comandos. Los hooks de comandos tienen un timeout por defecto. Si tu script tarda más que esto, Claude Code mata el proceso y procede sin la salida del hook. Para hooks PreToolUse, esto significa que la llamada a herramienta procede como si el hook no existiera. Para hooks PostToolUse, significa que el paso de logging o linting se omite silenciosamente.

Mantén los hooks de comandos rápidos. Un buen objetivo es menos de 500 milisegundos. Si tu hook necesita llamar a un servicio externo, considera si debería ser un hook HTTP en su lugar, ya que los hooks HTTP tienen timeouts configurables y están diseñados para operaciones de red.

Timeouts de hooks HTTP. Establece el campo timeout explícitamente en cada configuración de hook HTTP. El valor está en milisegundos. Para logging de auditoría donde no necesitas la respuesta, 3000-5000ms es razonable. Para hooks PreToolUse que toman decisiones de política remotas, mantenlo por debajo de 2000ms o los desarrolladores notarán el retraso en cada llamada a herramienta.

{
  "type": "http",
  "url": "https://audit.yourcompany.com/events",
  "timeout": 3000
}

Delegar trabajo lento. Si un hook necesita activar una operación lenta (ejecutar una suite de tests completa, generar un informe, enviar una notificación de Slack), lánzalo como proceso en segundo plano. El script del hook retorna inmediatamente y el proceso en segundo plano se completa por su cuenta.

#!/bin/bash
# .claude/hooks/async-notify.sh
# Disparar y olvidar: enviar notificación en segundo plano
(curl -s -X POST "https://slack.webhook.url" \
  -d "{\"text\": \"Claude Code session active\"}" &) 2>/dev/null

exit 0

Los paréntesis y & lanzan el comando curl en una subshell en segundo plano. El script del hook sale inmediatamente con código 0, así que Claude Code continúa sin esperar.

Medir tus hooks. Si una sesión se siente lenta después de añadir hooks, mide cada uno individualmente. Envuelve la invocación del hook en un script de medición:

#!/bin/bash
# .claude/hooks/timed-wrapper.sh
START=$(date +%s%N)
.claude/hooks/actual-hook.sh
EXIT_CODE=$?
END=$(date +%s%N)
DURATION_MS=$(( (END - START) / 1000000 ))
echo "Hook took ${DURATION_MS}ms" >&2
exit $EXIT_CODE

Si un hook supera consistentemente los 200ms, optimízalo o mueve el trabajo a un proceso en segundo plano. El desarrollo interactivo depende de bucles de feedback de menos de un segundo, y los hooks que rompen ese contrato degradan la experiencia para todos.

Combinando hooks con el sistema de permisos

Los hooks y los permisos son complementarios. El sistema de permisos define lo que Claude Code tiene permitido hacer. Los hooks definen lo que sucede cuando lo hace.

Un patrón potente es usar hooks PreToolUse para implementar permisos dinámicos. En lugar de una lista estática de permitidos, tu hook puede tomar decisiones en tiempo de ejecución basadas en el contexto. Por ejemplo, un hook podría permitir git push a ramas de funcionalidades pero denegarlo a main. O permitir ediciones de archivos durante el horario laboral pero bloquearlas durante la noche.

#!/bin/bash
# .claude/hooks/branch-guard.sh
COMMAND=$(jq -r '.tool_input.command // empty')

if echo "$COMMAND" | grep -q 'git push'; then
  BRANCH=$(git branch --show-current 2>/dev/null)
  if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
    jq -n '{
      hookSpecificOutput: {
        "hookEventName": "PreToolUse",
        "permissionDecision": "deny",
        "permissionDecisionReason": "Direct push to main/master blocked. Use a feature branch."
      }
    }'
    exit 0
  fi
fi

exit 0

Este tipo de política consciente del contexto es imposible solo con reglas de permisos estáticas. El sistema de hooks te da la programabilidad para implementar cualquier lógica que tu equipo necesite.

Consideraciones de seguridad de los hooks

Los hooks se ejecutan con los mismos privilegios que el usuario que ejecuta Claude Code. Un hook malicioso en el .claude/settings.json de un proyecto podría exfiltrar datos o modificar archivos silenciosamente. Hay varias salvaguardas contra esto.

Primero, los hooks se capturan al inicio de la sesión. Los cambios en las configuraciones de hooks durante una sesión no surten efecto hasta la siguiente sesión. Claude Code advierte al desarrollador si los archivos de configuración cambian y requiere revisión. Esto previene que pull requests maliciosos inyecten hooks que surtan efecto inmediatamente.

Segundo, el evento ConfigChange se dispara cuando cualquier archivo de configuración cambia durante una sesión. Puedes engancharte a este evento para alertar sobre modificaciones inesperadas de configuración.

Tercero, los administradores empresariales pueden establecer allowManagedHooksOnly en los ajustes gestionados para bloquear todos los hooks de usuario, proyecto y plugin. Solo se ejecutan los hooks gestionados centralmente. Consulta nuestra guía de ajustes gestionados empresariales para la configuración completa de bloqueo.

Cuarto, los hooks HTTP tienen restricciones allowedEnvVars y allowedHttpHookUrls que previenen que los hooks accedan a credenciales o alcancen endpoints que no han sido explícitamente aprobados.

Depuración de fallos en hooks

Los hooks fallan silenciosamente más a menudo de lo que esperarías. Cuando un hook no se comporta como se espera, aquí hay un enfoque sistemático para encontrar el problema.

El script del hook no es ejecutable. Este es el problema más común con diferencia. Si tu script de hook carece de permisos de ejecución, Claude Code no puede ejecutarlo. La solución es sencilla.

chmod +x .claude/hooks/your-hook.sh

Puedes verificar los permisos con ls -la .claude/hooks/ y buscar la flag x en la salida.

jq no está instalado o no está en el PATH. Si tu hook usa jq y no está disponible, el script falla en la primera llamada a jq. El resto del script nunca se ejecuta y no se produce ninguna salida JSON. Claude Code trata esto como un hook que no devolvió nada, así que procede como si el hook no existiera. Comprueba que jq está disponible ejecutando which jq en tu terminal.

El hook devuelve JSON inválido. Cuando un hook produce JSON malformado, Claude Code no puede analizar la respuesta. Esto típicamente ocurre cuando tu script mezcla sentencias echo con la salida de jq -n, o cuando un comando anterior en el script escribe texto inesperado a stdout. Mantén tus hooks limpios: escribe la salida de diagnóstico a stderr con >&2, y reserva stdout exclusivamente para la respuesta JSON.

#!/bin/bash
# Buena práctica: la salida de depuración va a stderr
echo "Hook triggered for tool: $(jq -r '.tool_name')" >&2

# Solo la decisión JSON va a stdout
jq -n '{
  hookSpecificOutput: {
    hookEventName: "PreToolUse",
    permissionDecision: "allow"
  }
}'

El hook expira. Los hooks de comandos tienen un timeout por defecto. Si tu hook llama a un servicio externo, ejecuta una suite de tests lenta o bloquea esperando entrada del usuario, puede exceder este límite. Claude Code termina el proceso del hook y procede sin su salida. Para hooks HTTP, puedes establecer un valor timeout explícito en milisegundos en la configuración. Para hooks de comandos, mantén la ejecución rápida y delega el trabajo lento a procesos en segundo plano.

El hook se ejecuta pero el matcher no coincide. El campo matcher es una expresión regular, no un glob. Si escribes "matcher": "*.sh", no coincidirá con nada útil. Para coincidir con comandos Bash, usa "Bash". Para coincidir con ediciones de archivos, usa "Edit|Write". Prueba tu regex por separado antes de añadirla a la configuración del hook.

Depuración con logging a stderr. La forma más rápida de depurar cualquier hook es escribir información de diagnóstico a stderr. Claude Code no consume la salida stderr de los hooks, así que aparece en el terminal donde Claude Code se está ejecutando.

#!/bin/bash
# .claude/hooks/debug-example.sh
echo "DEBUG: Hook fired at $(date)" >&2
echo "DEBUG: Input JSON:" >&2
cat | tee /tmp/hook-input.json | jq -r '.tool_name' >&2

# Tu lógica real del hook aquí
TOOL_NAME=$(jq -r '.tool_name' < /tmp/hook-input.json)
echo "DEBUG: Tool name is $TOOL_NAME" >&2

Esto escribe una copia del JSON de entrada en /tmp/hook-input.json para que puedas inspeccionarlo después, e imprime el nombre de la herramienta a stderr para que puedas verlo en tiempo real.

Mensajes de error comunes y qué significan. Si ves "hook returned non-zero exit code", tu script encontró un error. Comprueba el script manualmente canalizando JSON de ejemplo: echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | .claude/hooks/your-hook.sh. Si ves "permission denied", el script no es ejecutable. Si ves "command not found", la línea shebang está mal o la ruta del script en settings.json es incorrecta.

Integración de hooks con pipelines de CI/CD

Los hooks no se limitan a sesiones de desarrollo interactivas. Cuando Claude Code se ejecuta en un pipeline de CI/CD, los hooks proporcionan los mismos eventos del ciclo de vida, lo que significa que puedes hacer cumplir políticas y recopilar datos de auditoría en flujos de trabajo automatizados.

En un contexto de GitHub Actions, puedes incluir configuraciones de hooks en el .claude/settings.json de tu repositorio y se aplicarán cada vez que Claude Code se ejecute como parte de un workflow. Esto es particularmente útil para hacer cumplir estándares de código, bloquear patrones prohibidos y registrar todas las acciones tomadas durante la generación o revisión automática de código.

Una configuración típica de hooks para CI/CD incluye un guardia PreToolUse que previene que Claude Code modifique archivos fuera del alcance esperado, y un logger PostToolUse que registra cada acción con fines de cumplimiento.

#!/bin/bash
# .claude/hooks/ci-guard.sh
# Restringir modificaciones de archivos solo a los archivos cambiados del PR
TOOL_NAME=$(jq -r '.tool_name // empty')
FILE_PATH=$(jq -r '.tool_input.file_path // .tool_input.path // empty')

if [ -z "$FILE_PATH" ]; then
  exit 0
fi

if [ "$TOOL_NAME" = "Edit" ] || [ "$TOOL_NAME" = "Write" ]; then
  # Comprobar si el archivo está en la lista de archivos cambiados para este PR
  CHANGED_FILES=$(git diff --name-only origin/main...HEAD 2>/dev/null)
  if ! echo "$CHANGED_FILES" | grep -qF "$FILE_PATH"; then
    jq -n '{
      hookSpecificOutput: {
        hookEventName: "PreToolUse",
        permissionDecision: "deny",
        permissionDecisionReason: "CI mode: only files changed in this PR may be modified"
      }
    }'
  fi
fi

Para hooks HTTP en CI/CD, puedes enviar datos de eventos a tu infraestructura de logging para construir un registro de auditoría de cada acción que Claude Code realiza durante ejecuciones automatizadas. Esto es particularmente valioso para industrias reguladas donde necesitas demostrar qué hizo un agente de IA y por qué.

Para un tutorial completo sobre ejecutar Claude Code en GitHub Actions, incluyendo configuración de hooks, consulta nuestra guía de Claude Code GitHub Actions.

La lección

Claude Code no es solo un asistente de programación. Es un sistema basado en eventos con una API completa de ciclo de vida. Cada acción que realiza dispara un evento. Cada evento puede activar tu código. Eso cambia la relación de supervisión reactiva a automatización proactiva.

Los hooks más valiosos no son complicados. Auto-lint en cambio de archivo. Bloquear comandos destructivos. Registrar todo en un endpoint central. Cada uno es un script corto que lleva minutos escribir y ahorra horas de supervisión manual.

El patrón que importa es pensar en las sesiones de Claude Code como pipelines. La entrada entra, los eventos se disparan, los hooks reaccionan, la salida sale. Una vez que lo ves así, la pregunta deja de ser "¿qué debería aprobar?" y pasa a ser "¿qué debería automatizar?"

Conclusión

Esta guía empezó con supervisión reactiva. Aprobando llamadas a herramientas manualmente, revisando salidas después del hecho, detectando problemas cuando ya habían sucedido.

El sistema de hooks invierte eso completamente. Los hooks PreToolUse detectan problemas antes de que sucedan. Los hooks PostToolUse automatizan la respuesta. Los hooks HTTP te dan visibilidad en todo tu equipo.

Empieza con un hook. El manejador PostToolUse de auto-lint es la victoria más fácil. Una vez que funcione, añade un guardia PreToolUse para tu error más común. Luego añade el hook HTTP para logging. Cada uno lleva diez minutos y se acumula en cada sesión. Para extender esta automatización a tu pipeline de CI/CD, consulta nuestras Recetas de Claude Code para GitHub Actions para definiciones de workflow que ejecutan Claude en cada pull request.

La referencia de hooks documenta cada evento disponible, los esquemas JSON completos y las opciones de configuración. La guía de hooks tiene más ejemplos. Y si necesitas gestión centralizada de hooks en toda una organización, lee nuestra guía de ajustes gestionados empresariales para la configuración allowManagedHooksOnly.