Hooks y Automatizacion
Objetivo
Comprender qué son los hooks en Claude Code, cómo funcionan y cuándo utilizarlos para automatizar acciones en tu flujo de trabajo.
Introducción
Los hooks son scripts que se ejecutan automáticamente en momentos específicos cuando trabajas con Claude Code. Piensa en ellos como "respuestas automáticas" a ciertos eventos que ocurren durante tu sesión.
Definición formal:
Los hooks son comandos personalizados que se ejecutan de forma determinística en puntos específicos del ciclo de vida de Claude Code, sin necesidad de invocación manual.
En términos simples: son acciones que suceden automáticamente cuando Claude hace algo, sin que tú o Claude tengan que pedirlo explícitamente.
¿Por qué usar hooks?
Imagina este escenario común: cada vez que Claude modifica un archivo TypeScript, tú quieres que se formatee automáticamente con Prettier. Sin hooks, tendrías que:
- Esperar a que Claude termine la edición
- Recordar ejecutar
npx prettier --write archivo.ts - Repetir esto para cada archivo modificado
Con hooks, esto sucede automáticamente. Claude modifica el archivo → el hook ejecuta Prettier → el código queda formateado sin intervención manual.
Beneficios clave
- Automatización: Elimina tareas repetitivas
- Consistencia: Las acciones siempre se ejecutan, sin olvidos
- Eficiencia: Ahorra tiempo en cada sesión
- Calidad: Asegura que ciertas validaciones o procesos siempre ocurran
Concepto clave: Eventos y acciones
Los hooks funcionan con un patrón simple:
EVENTO → ACCIÓN AUTOMÁTICAEjemplo 1: Formateo automático
Evento: Claude edita un archivo .ts
Acción: Ejecutar prettier automáticamenteEjemplo 2: Tests automáticos
Evento: Claude modifica código en /src
Acción: Ejecutar tests relacionados con ese archivoEjemplo 3: Logging de comandos
Evento: Claude ejecuta un comando bash
Acción: Registrar el comando en un archivo de logEjemplo 4: Notificaciones
Evento: Claude termina de responder
Acción: Enviar notificación de escritorioTipos de eventos disponibles
Claude Code detecta automáticamente estos eventos durante tu sesión:
| Evento | Cuándo ocurre |
|---|---|
SessionStart | Al iniciar o reanudar una sesión |
UserPromptSubmit | Cuando envías un mensaje a Claude |
PreToolUse | Antes de que Claude use una herramienta |
PostToolUse | Después de que Claude use una herramienta exitosamente |
Stop | Cuando Claude termina de responder |
SessionEnd | Al finalizar la sesión |
Nota: En el punto 5.2 veremos todos los tipos de eventos en detalle.
¿Qué tipo de acciones puedes automatizar?
Los hooks pueden ejecutar cualquier comando de shell. Casos de uso comunes:
Calidad de código
- Formatear con Prettier, Black, etc.
- Ejecutar linters (ESLint, Pylint)
- Validar sintaxis
Testing
- Ejecutar tests unitarios relacionados
- Ejecutar tests de integración
- Verificar cobertura de código
Seguridad
- Validar que no se modifiquen archivos sensibles
- Escanear en busca de secretos hardcodeados
- Bloquear comandos peligrosos
Productividad
- Registrar comandos ejecutados (auditoría)
- Enviar notificaciones
- Auto-commit de cambios
- Sincronizar con herramientas externas
Hooks vs. Skills vs. Comandos manuales
Es importante entender la diferencia entre estos tres conceptos:
| Característica | Hooks | Skills | Comandos manuales |
|---|---|---|---|
| Invocación | Automática (por evento) | Manual con /nombre | Manual (escribes el comando) |
| Cuándo usar | Automatizar tareas repetitivas | Encapsular flujos complejos | Tareas puntuales |
| Configuración | settings.json | .claude/skills/ | N/A |
| Puede bloquear acciones | Sí | No | No |
Ejemplo comparativo
Tarea: Formatear código TypeScript
Opción 1 - Comando manual:
> Ejecuta prettier en src/utils.ts- Debes pedirlo cada vez
- Fácil de olvidar
Opción 2 - Skill:
> /format src/utils.ts- Más rápido que escribir el comando completo
- Aún requiere invocación manual
Opción 3 - Hook:
- Se ejecuta automáticamente cada vez que Claude edita un
.ts - Nunca lo olvidas
- Cero intervención manual
¿Cuándo usar hooks?
Usa hooks cuando
- La acción debe ocurrir siempre tras un evento específico
- Quieres automatizar tareas repetitivas
- Necesitas garantizar que ciertas validaciones siempre se ejecuten
- Quieres mantener estándares de calidad sin intervención manual
No uses hooks cuando
- La acción es opcional o contextual
- Solo necesitas ejecutar algo una vez
- La acción es compleja y requiere decisiones del LLM
- Puede ralentizar significativamente el flujo de trabajo
Regla general
Si te encuentras pidiendo a Claude que haga lo mismo repetidamente, probablemente deberías usar un hook.
Ejemplo práctico: El flujo completo
Imagina que estás desarrollando una aplicación en Python. Quieres que:
- Todo el código se formatee con Black automáticamente
- Se ordenen los imports con isort
- Se ejecuten los tests relacionados
Sin hooks:
Tú: Modifica la función calculate_total en src/utils.py
Claude: [Modifica el archivo]
Tú: Ejecuta black en ese archivo
Claude: [Ejecuta black]
Tú: Ahora ejecuta isort
Claude: [Ejecuta isort]
Tú: Ejecuta los tests relacionados
Claude: [Ejecuta tests]Con hooks:
Tú: Modifica la función calculate_total en src/utils.py
Claude: [Modifica el archivo]
→ Hook 1: Ejecuta black automáticamente
→ Hook 2: Ejecuta isort automáticamente
→ Hook 3: Ejecuta tests automáticamenteResultado: Lo que antes requería 4 mensajes, ahora sucede con 1.
Vista previa: Lo que aprenderás próximamente
En los siguientes puntos del módulo profundizaremos en:
5.2 Tipos de hooks
- Todos los eventos disponibles
- Cuándo se dispara cada uno
- Variables disponibles en cada evento
5.3 Configuración de hooks
- Cómo configurar hooks en
settings.json - Matchers para filtrar eventos
- Variables de entorno disponibles
- Códigos de salida y control de flujo
5.4 Casos de uso avanzados
- Auto-commit después de cambios
- Ejecutar tests relacionados
- Notificaciones de sistema
- Integración con herramientas externas
5.5 Timeout de hooks
- Configuración de timeouts
- Manejo de hooks lentos
- Mejores prácticas de rendimiento
Puntos clave
- Los hooks son scripts que se ejecutan automáticamente en eventos específicos
- Siguen el patrón EVENTO → ACCIÓN AUTOMÁTICA
- Se usan para automatizar tareas repetitivas y garantizar consistencia
- Son determinísticos: siempre se ejecutan, sin depender del LLM
- Se configuran en
settings.json(global o por proyecto) - Diferentes de skills (manuales) y comandos (puntuales)
Recursos adicionales
- Documentación oficial de hooks
- Referencia completa de hooks
- Configuración de settings.json
Próximo paso: 5.2 Tipos de hooks - Conoce todos los eventos disponibles y cuándo usar cada uno.
5.2 Tipos de hooks
Objetivo
Conocer todos los tipos de hooks disponibles en Claude Code, cuándo se disparan, qué información proporcionan y cómo elegir el hook adecuado para cada necesidad.
Introducción
Claude Code ofrece 13 tipos de hooks que cubren todo el ciclo de vida de una sesión. Cada hook se dispara en un momento específico y proporciona información contextual diferente.
En este punto veremos todos los tipos disponibles, organizados en categorías lógicas para facilitar su comprensión.
Vista general: Todos los hooks disponibles
| Hook | Cuándo se ejecuta | Categoría |
|---|---|---|
SessionStart | Al iniciar o reanudar una sesión | Ciclo de sesión |
SessionEnd | Al finalizar la sesión | Ciclo de sesión |
Setup | Con flags --init, --init-only o --maintenance | Ciclo de sesión |
UserPromptSubmit | Cuando el usuario envía un mensaje | Interacción |
Stop | Cuando Claude termina de responder | Interacción |
PreToolUse | Antes de ejecutar una herramienta | Herramientas |
PostToolUse | Después de ejecutar una herramienta (éxito) | Herramientas |
PostToolUseFailure | Después de ejecutar una herramienta (fallo) | Herramientas |
PermissionRequest | Cuando aparece un diálogo de permiso | Herramientas |
SubagentStart | Cuando se inicia un subagente | Subagentes |
SubagentStop | Cuando termina un subagente | Subagentes |
PreCompact | Antes de compactar el contexto | Especiales |
Notification | Cuando Claude Code envía notificaciones | Especiales |
Categoría 1: Hooks de ciclo de sesión
Estos hooks se ejecutan al inicio, durante el setup y al final de una sesión.
SessionStart
Cuándo se ejecuta: Al iniciar una nueva sesión o al reanudar una sesión existente.
Casos de uso:
- Inicializar variables de entorno específicas de la sesión
- Cargar configuración dinámica
- Registrar el inicio de sesión (auditoría)
- Verificar dependencias necesarias
Variables especiales:
$CLAUDE_ENV_FILE: Ruta al archivo donde puedes persistir variables de entorno para la sesión
Ejemplo práctico:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo \"SESSION_ID=$(date +%s)\" > \"$CLAUDE_ENV_FILE\""
}
]
}
]
}
}SessionEnd
Cuándo se ejecuta: Al finalizar la sesión (cierre normal).
Casos de uso:
- Limpiar archivos temporales
- Guardar logs de la sesión
- Enviar reportes o estadísticas
- Restaurar configuración original
Ejemplo práctico:
{
"hooks": {
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "echo \"Sesión finalizada: $(date)\" >> ~/.claude/session-log.txt"
}
]
}
]
}
}Setup
Cuándo se ejecuta: Cuando se usa alguno de estos flags:
claude --init(inicializar proyecto)claude --init-only(solo configuración inicial)claude --maintenance(mantenimiento)
Casos de uso:
- Configuración inicial de proyecto
- Instalación de dependencias
- Generación de archivos de configuración
- Verificación de herramientas necesarias
Ejemplo práctico:
{
"hooks": {
"Setup": [
{
"hooks": [
{
"type": "command",
"command": "npm install && git config --local core.hooksPath .claude/git-hooks"
}
]
}
]
}
}Categoría 2: Hooks de interacción
Estos hooks se ejecutan durante la interacción entre tú y Claude.
UserPromptSubmit
Cuándo se ejecuta: Inmediatamente después de que envías un mensaje a Claude, antes de que Claude lo procese.
Casos de uso:
- Validar que tu prompt cumple ciertas reglas
- Añadir contexto adicional automáticamente
- Registrar todos los prompts (auditoría)
- Bloquear prompts que contengan información sensible
Características especiales:
- Puede bloquear el prompt (impedir que llegue a Claude)
- El output del hook se añade al contexto de Claude
- Útil para inyectar información dinámica
Ejemplo práctico:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "echo \"Contexto adicional: Branch actual = $(git branch --show-current)\""
}
]
}
]
}
}Bloqueando un prompt:
#!/usr/bin/env bash
# .claude/hooks/validate-prompt.sh
# Lee el prompt del usuario
prompt=$(jq -r '.user_prompt' | tr '[:upper:]' '[:lower:]')
# Bloquea si contiene palabras sensibles
if echo "$prompt" | grep -qE '(password|secret|api[_-]?key)'; then
echo '{"decision": "block", "reason": "El prompt contiene información sensible"}'
exit 0
fiStop
Cuándo se ejecuta: Cuando Claude termina de responder completamente.
Casos de uso:
- Enviar notificación de que Claude terminó
- Validar que Claude completó todas las tareas
- Decidir si Claude debe continuar trabajando
- Registrar el final de una interacción
Características especiales:
- Puede bloquear para que Claude continúe trabajando automáticamente
- Soporta hooks de tipo
prompt(usa Haiku para decidir)
Ejemplo práctico - Notificación:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Claude ha terminado de responder'"
}
]
}
]
}
}Ejemplo avanzado - Continuar automáticamente:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Evalúa si Claude debe continuar trabajando basándote en:\n$ARGUMENTS\n\nResponde JSON: {\"ok\": true} para continuar o {\"ok\": false, \"reason\": \"explicación\"} para detener",
"timeout": 30
}
]
}
]
}
}Categoría 3: Hooks de herramientas
Estos hooks se disparan cuando Claude usa herramientas (Read, Write, Edit, Bash, etc.).
PreToolUse
Cuándo se ejecuta: Justo antes de que Claude ejecute una herramienta.
Casos de uso:
- Validar que el comando/operación es segura
- Bloquear operaciones sobre archivos sensibles
- Registrar qué herramientas está usando Claude (auditoría)
- Modificar parámetros de la herramienta antes de ejecutarla
- Pre-validación de permisos adicional
Características especiales:
- Puede bloquear la ejecución de la herramienta
- Puede modificar los parámetros de entrada
- Requiere configuración de
matcherpara filtrar herramientas
Herramientas disponibles para matchers:
Read,Write,Edit,Bash,Glob,Grep,TaskWebFetch,WebSearchmcp__<server>__<tool>(herramientas MCP)
Ejemplo práctico - Bloquear archivos sensibles:
#!/usr/bin/env bash
# .claude/hooks/block-sensitive.sh
file_path=$(jq -r '.tool_input.file_path // empty')
# Lista de patterns prohibidos
if echo "$file_path" | grep -qE '(\.env|\.git/|node_modules/|\.ssh/)'; then
# Exit 2 = Bloquear operación
echo "Acceso denegado a archivo sensible: $file_path" >&2
exit 2
fi
exit 0Configuración:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read|Write|Edit",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/block-sensitive.sh"
}
]
}
]
}
}Ejemplo - Logging de comandos bash:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.command' >> ~/.claude/bash-commands.log"
}
]
}
]
}
}PostToolUse
Cuándo se ejecuta: Justo después de que Claude ejecute una herramienta exitosamente.
Casos de uso:
- Formatear código automáticamente después de ediciones
- Ejecutar linters después de modificar archivos
- Ejecutar tests relacionados
- Sincronizar con herramientas externas
- Registrar operaciones completadas
Características especiales:
- Solo se ejecuta si la herramienta tuvo éxito (exit 0)
- Requiere configuración de
matcher - El más usado para automatización de calidad de código
Ejemplo práctico - Formateo automático con Prettier:
#!/usr/bin/env bash
# .claude/hooks/auto-format.sh
file_path=$(jq -r '.tool_input.file_path')
# Solo formatear archivos TypeScript/JavaScript
if echo "$file_path" | grep -qE '\.(ts|tsx|js|jsx)$'; then
npx prettier --write "$file_path" 2>/dev/null
fiConfiguración:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/auto-format.sh",
"timeout": 30
}
]
}
]
}
}Ejemplo - Tests automáticos:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "file=$(jq -r '.tool_input.file_path'); npm test -- --findRelatedTests \"$file\" --passWithNoTests",
"timeout": 120
}
]
}
]
}
}PostToolUseFailure
Cuándo se ejecuta: Cuando una herramienta falla durante su ejecución.
Casos de uso:
- Registrar errores para diagnóstico
- Enviar alertas de fallos
- Ejecutar acciones de recuperación
- Rollback de cambios parciales
Ejemplo práctico:
{
"hooks": {
"PostToolUseFailure": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo \"[$(date)] Comando falló\" >> ~/.claude/errors.log && jq '.' >> ~/.claude/errors.log"
}
]
}
]
}
}PermissionRequest
Cuándo se ejecuta: Cuando Claude necesita pedir permiso al usuario para ejecutar una herramienta.
Casos de uso:
- Pre-aprobar ciertos comandos automáticamente
- Registrar solicitudes de permiso
- Añadir validaciones adicionales antes del diálogo
Características especiales:
- Puede auto-aprobar o auto-denegar permisos
- Útil para automatizar flujos en entornos confiables
Ejemplo práctico:
#!/usr/bin/env bash
# Auto-aprobar comandos git read-only
command=$(jq -r '.tool_input.command // empty')
# Auto-aprobar comandos git de solo lectura
if echo "$command" | grep -qE '^git (status|log|diff|show|branch)'; then
echo '{"hookSpecificOutput": {"hookEventName": "PermissionRequest", "permissionDecision": "allow"}}'
exit 0
fi
# Para todo lo demás, preguntar al usuario
echo '{"hookSpecificOutput": {"hookEventName": "PermissionRequest", "permissionDecision": "ask"}}'
exit 0Categoría 4: Hooks de subagentes
Estos hooks se ejecutan cuando se trabaja con subagentes (instancias especializadas de Claude).
SubagentStart
Cuándo se ejecuta: Cuando Claude inicia un subagente especializado.
Casos de uso:
- Registrar inicio de subagentes para auditoría
- Cargar configuración específica para el subagente
- Notificar que comenzó una tarea en background
Ejemplo práctico:
{
"hooks": {
"SubagentStart": [
{
"hooks": [
{
"type": "command",
"command": "echo \"[$(date)] Subagente iniciado\" >> ~/.claude/subagents.log"
}
]
}
]
}
}SubagentStop
Cuándo se ejecuta: Cuando un subagente termina su trabajo.
Casos de uso:
- Notificar que el subagente terminó
- Validar resultados del subagente
- Decidir si el subagente debe continuar
Características especiales:
- Puede bloquear para que el subagente continúe
- Soporta hooks de tipo
prompt
Ejemplo práctico:
{
"hooks": {
"SubagentStop": [
{
"hooks": [
{
"type": "command",
"command": "notify-send 'Subagente completado' 'El subagente ha terminado su tarea'"
}
]
}
]
}
}Categoría 5: Hooks especiales
PreCompact
Cuándo se ejecuta: Justo antes de que Claude compacte el contexto de la conversación (comando /compact).
Casos de uso:
- Guardar snapshot del contexto completo antes de compactar
- Registrar estadísticas de la sesión
- Exportar información importante antes de la compactación
Ejemplo práctico:
{
"hooks": {
"PreCompact": [
{
"hooks": [
{
"type": "command",
"command": "echo \"Compactación iniciada: $(date)\" >> ~/.claude/compact-log.txt"
}
]
}
]
}
}Notification
Cuándo se ejecuta: Cuando Claude Code envía notificaciones del sistema.
Casos de uso:
- Integrar con sistemas de notificaciones personalizados
- Filtrar o modificar notificaciones
- Registrar todas las notificaciones
Ejemplo práctico:
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "jq -r '.message' | xargs -I {} notify-send 'Claude Code' '{}'"
}
]
}
]
}
}Guía de selección: ¿Qué hook usar?
| Necesitas... | Usa este hook |
|---|---|
| Formatear código después de editar | PostToolUse con matcher Write|Edit |
| Ejecutar tests automáticamente | PostToolUse con matcher Write|Edit |
| Bloquear archivos sensibles | PreToolUse con matcher Read|Write|Edit |
| Registrar comandos bash | PreToolUse con matcher Bash |
| Notificar cuando Claude termine | Stop |
| Inicializar variables de sesión | SessionStart |
| Validar prompts del usuario | UserPromptSubmit |
| Limpiar al finalizar | SessionEnd |
| Auto-aprobar comandos git seguros | PermissionRequest con matcher Bash |
| Configuración inicial de proyecto | Setup |
| Registrar errores de herramientas | PostToolUseFailure |
| Notificar fin de subagente | SubagentStop |
Matchers: Filtrado de herramientas
Para los hooks PreToolUse, PostToolUse, PostToolUseFailure y PermissionRequest, debes especificar qué herramientas disparan el hook usando matchers.
Sintaxis de matchers
{
"matcher": "PATRÓN"
}Patrones válidos:
"Bash"→ Solo herramienta Bash"Write"→ Solo herramienta Write"Write|Edit"→ Write o Edit (regex con pipe)"*"o""→ Todas las herramientas"mcp__github__*"→ Todas las herramientas del servidor MCP de GitHub
Ejemplos de matchers
// Solo para archivos Python
{
"matcher": "Write|Edit",
"hooks": [/* ... */]
}
// Solo comandos bash
{
"matcher": "Bash",
"hooks": [/* ... */]
}
// Todas las herramientas
{
"matcher": "*",
"hooks": [/* ... */]
}Resumen visual: Línea de tiempo de hooks
claude (sesión inicia)
│
├─→ SessionStart
│
├─→ UserPromptSubmit ──→ [Usuario envía mensaje]
│
├─→ PreToolUse ──────────→ [Claude va a usar herramienta]
│
├─→ PostToolUse ─────────→ [Herramienta ejecutada con éxito]
│ o
├─→ PostToolUseFailure ──→ [Herramienta falló]
│
├─→ Stop ────────────────→ [Claude terminó de responder]
│
├─→ /compact (manual)
│ └─→ PreCompact
│
└─→ SessionEnd (al cerrar)Puntos clave
- Claude Code ofrece 13 tipos de hooks que cubren todo el ciclo de vida
- Los hooks se clasifican en: sesión, interacción, herramientas, subagentes y especiales
- Los hooks más usados son:
PostToolUse(automatización) yPreToolUse(validación) - Los hooks
PreToolUse,PostToolUse,PermissionRequestrequieren matchers - Los hooks pueden bloquear operaciones con exit code 2
- Cada hook proporciona información contextual específica vía stdin JSON
- Variables de entorno como
$CLAUDE_PROJECT_DIRestán disponibles en todos los hooks
Recursos adicionales
- Documentación oficial de hooks
- Referencia completa de hooks
- Ejemplos de JSON de entrada por hook
Próximo paso: 5.3 Configuración de hooks - Aprende a configurar hooks en settings.json con ejemplos prácticos.
5.3 Configuración de hooks
Objetivo
Aprender a configurar hooks en settings.json, entender las variables disponibles, códigos de salida, timeouts y mejores prácticas para crear configuraciones robustas y mantenibles.
Introducción
Los hooks se configuran en archivos settings.json usando una estructura JSON específica. En este punto aprenderás todo lo necesario para crear configuraciones efectivas, desde lo más básico hasta patrones avanzados.
Dónde configurar hooks
Claude Code sigue una jerarquía de configuración:
1. ~/.claude/settings.json → Configuración GLOBAL (todos los proyectos)
2. .claude/settings.json → Configuración del PROYECTO (en git)
3. .claude/settings.local.json → Configuración LOCAL (no commitida)Prioridad: Los archivos más específicos sobrescriben a los más generales.
¿Dónde colocar cada hook?
| Hook | Recomendación | Motivo |
|---|---|---|
| Formateo de código | Proyecto (.claude/settings.json) | Todo el equipo se beneficia |
| Logging personal | Global (~/.claude/settings.json) | Es preferencia personal |
| API keys o tokens | Local (.claude/settings.local.json) | No debe estar en git |
| Tests automáticos | Proyecto | Garantiza calidad para todos |
| Notificaciones | Global | Preferencia personal |
Estructura básica
La configuración de hooks va dentro de la sección hooks de settings.json:
{
"hooks": {
"TIPO_DE_HOOK": [
{
"matcher": "PATRÓN", // Solo para PreToolUse, PostToolUse, PermissionRequest
"hooks": [
{
"type": "command",
"command": "comando a ejecutar",
"timeout": 60
}
]
}
]
}
}Campos principales
| Campo | Descripción | Requerido |
|---|---|---|
type | Tipo de hook: "command" o "prompt" | Sí |
command | Comando shell a ejecutar | Sí (si type=command) |
prompt | Prompt para evaluación con LLM | Sí (si type=prompt) |
timeout | Timeout en segundos | No (default: 600s) |
matcher | Patrón para filtrar herramientas | Solo en PreToolUse, PostToolUse, etc. |
Anatomía de un hook
Veamos paso a paso cómo crear un hook completo.
Ejemplo 1: Hook simple (inline)
Formatear archivos TypeScript después de editarlos:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | { read f; [[ \"$f\" =~ \\.tsx?$ ]] && npx prettier --write \"$f\"; }",
"timeout": 30
}
]
}
]
}
}Desglose:
"PostToolUse": Se ejecuta después de usar una herramienta"matcher": "Write|Edit": Solo cuando se use Write o Editjq -r '.tool_input.file_path': Extrae la ruta del archivo del JSON de entrada[[ "$f" =~ \.tsx?$ ]]: Verifica que sea .ts o .tsxnpx prettier --write "$f": Formatea el archivo"timeout": 30: Máximo 30 segundos
Ejemplo 2: Hook con script externo
Es más limpio separar la lógica en archivos:
Estructura de archivos:
.claude/
├── settings.json
└── hooks/
└── format-typescript.sh.claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format-typescript.sh",
"timeout": 30
}
]
}
]
}
}.claude/hooks/format-typescript.sh:
#!/usr/bin/env bash
set -euo pipefail
# Leer el JSON de entrada desde stdin
file_path=$(jq -r '.tool_input.file_path')
# Verificar que es un archivo TypeScript/JavaScript
if [[ "$file_path" =~ \.(ts|tsx|js|jsx)$ ]]; then
echo "Formateando: $file_path" >&2
npx prettier --write "$file_path" 2>&1
fi
exit 0No olvides dar permisos de ejecución:
chmod +x .claude/hooks/format-typescript.shVariables disponibles
Los hooks reciben información de dos formas:
1. Variables de entorno
Disponibles como $VARIABLE en bash:
| Variable | Descripción | Disponible en |
|---|---|---|
$CLAUDE_PROJECT_DIR | Ruta absoluta al directorio del proyecto | Todos los hooks |
$CLAUDE_ENV_FILE | Archivo para persistir variables de sesión | SessionStart |
$CLAUDE_CODE_REMOTE | "true" si está en modo remoto, vacío en CLI | Todos los hooks |
Ejemplo:
#!/usr/bin/env bash
echo "Proyecto: $CLAUDE_PROJECT_DIR"
echo "Es remoto: $CLAUDE_CODE_REMOTE"2. JSON de entrada (stdin)
Cada hook recibe un JSON con información contextual vía stdin.
Campos comunes (todos los hooks)
{
"session_id": "abc123def456",
"transcript_path": "/ruta/al/transcript.jsonl",
"cwd": "/directorio/actual",
"permission_mode": "default",
"hook_event_name": "PostToolUse"
}Campos específicos para hooks de herramientas
PreToolUse / PostToolUse con Write:
{
"tool_name": "Write",
"tool_input": {
"file_path": "/ruta/absoluta/archivo.ts",
"content": "contenido del archivo..."
},
"tool_use_id": "toolu_01ABC123..."
}PreToolUse / PostToolUse con Edit:
{
"tool_name": "Edit",
"tool_input": {
"file_path": "/ruta/absoluta/archivo.ts",
"old_string": "texto antiguo",
"new_string": "texto nuevo",
"replace_all": false
}
}PreToolUse / PostToolUse con Bash:
{
"tool_name": "Bash",
"tool_input": {
"command": "npm test",
"description": "Ejecutar tests",
"timeout": 120000,
"run_in_background": false
}
}Cómo usar el JSON de entrada
Con jq (recomendado):
#!/usr/bin/env bash
# Extraer campos específicos
file_path=$(jq -r '.tool_input.file_path')
tool_name=$(jq -r '.tool_name')
command=$(jq -r '.tool_input.command // empty')
echo "Herramienta: $tool_name"
echo "Archivo: $file_path"Sin jq (alternativa básica):
#!/usr/bin/env bash
# Leer todo el stdin
json_input=$(cat)
# Extraer con grep/sed (menos robusto)
file_path=$(echo "$json_input" | grep -oP '"file_path":\s*"\K[^"]+')Códigos de salida y control de flujo
Los hooks se comunican usando códigos de salida:
| Código | Significado | Efecto |
|---|---|---|
0 | Éxito | Continúa normalmente. stdout se muestra en modo verbose |
2 | Error bloqueante | BLOQUEA la operación. stderr se muestra al usuario |
1 u otros | Error no bloqueante | Se registra pero no bloquea. stderr en verbose |
Ejemplo: Bloquear operaciones peligrosas
#!/usr/bin/env bash
# .claude/hooks/block-dangerous.sh
command=$(jq -r '.tool_input.command // empty')
# Lista de comandos peligrosos
if echo "$command" | grep -qE '(rm -rf|sudo|mkfs|dd if=|:(){ :|:& };:)'; then
echo "BLOQUEADO: Comando peligroso detectado" >&2
exit 2 # Bloquea la ejecución
fi
# Comando permitido
exit 0Configuración:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/block-dangerous.sh"
}
]
}
]
}
}JSON de salida avanzado
Los hooks pueden devolver JSON en stdout (solo con exit 0) para comunicar decisiones complejas.
Para PreToolUse
#!/usr/bin/env bash
file_path=$(jq -r '.tool_input.file_path')
if [[ "$file_path" == *".env"* ]]; then
# Denegar con JSON
cat <<EOF
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "No se pueden modificar archivos .env"
}
}
EOF
exit 0
fi
# Permitir
cat <<EOF
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow"
}
}
EOF
exit 0Para UserPromptSubmit
#!/usr/bin/env bash
prompt=$(jq -r '.user_prompt' | tr '[:upper:]' '[:lower:]')
if echo "$prompt" | grep -qE '(password|secret|token)'; then
# Bloquear prompt
cat <<EOF
{
"decision": "block",
"reason": "El prompt contiene información sensible",
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit"
}
}
EOF
exit 0
fi
# Permitir y añadir contexto
cat <<EOF
{
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "Branch actual: $(git branch --show-current 2>/dev/null || echo 'no-git')"
}
}
EOF
exit 0Timeouts
Cada hook puede tener un timeout personalizado:
{
"type": "command",
"command": "npm test",
"timeout": 300
}Valores:
- Default: 600 segundos (10 minutos)
- Mínimo recomendado: 10 segundos
- Máximo: Sin límite técnico, pero considera la experiencia de usuario
Guía de timeouts recomendados
| Operación | Timeout recomendado |
|---|---|
| Formateo (prettier, black) | 30-60 segundos |
| Linting (eslint, pylint) | 60-120 segundos |
| Tests unitarios | 120-300 segundos |
| Tests de integración | 300-600 segundos |
| Operaciones de red | 60-180 segundos |
| Logging/auditoría | 10-30 segundos |
Múltiples hooks para el mismo evento
Puedes configurar múltiples hooks que se ejecutan secuencialmente:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/format.sh",
"timeout": 30
},
{
"type": "command",
"command": ".claude/hooks/lint.sh",
"timeout": 60
},
{
"type": "command",
"command": ".claude/hooks/test.sh",
"timeout": 120
}
]
}
]
}
}Orden de ejecución: De arriba hacia abajo.
Si un hook falla:
- Exit 0: Continúa al siguiente
- Exit 2: Bloquea y no ejecuta los siguientes
- Exit 1 u otros: Registra error pero continúa
Ejemplo completo: Proyecto real
Configuración completa para un proyecto TypeScript/React:
.claude/settings.json:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo \"SESSION_START=$(date +%s)\" > \"$CLAUDE_ENV_FILE\" && echo \"Sesión iniciada: $(date)\"",
"timeout": 10
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/log-bash.sh",
"timeout": 10
},
{
"type": "command",
"command": ".claude/hooks/block-dangerous.sh",
"timeout": 10
}
]
},
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/block-sensitive-files.sh",
"timeout": 10
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/format-and-lint.sh",
"timeout": 60
},
{
"type": "command",
"command": ".claude/hooks/run-tests.sh",
"timeout": 180
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Claude ha terminado'",
"timeout": 5
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "echo \"Sesión finalizada: $(date)\" >> ~/.claude/session-history.log",
"timeout": 10
}
]
}
]
}
}.claude/hooks/log-bash.sh:
#!/usr/bin/env bash
set -euo pipefail
command=$(jq -r '.tool_input.command')
description=$(jq -r '.tool_input.description // "Sin descripción"')
echo "[$(date -Iseconds)] $description: $command" >> "$CLAUDE_PROJECT_DIR/.claude/bash-log.txt"
exit 0.claude/hooks/block-dangerous.sh:
#!/usr/bin/env bash
set -euo pipefail
command=$(jq -r '.tool_input.command // empty')
if echo "$command" | grep -qE '(rm -rf|sudo |mkfs|dd if=)'; then
echo "ERROR: Comando peligroso bloqueado" >&2
exit 2
fi
exit 0.claude/hooks/block-sensitive-files.sh:
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
if echo "$file_path" | grep -qE '(\.env|\.git/|\.ssh/|credentials|secrets)'; then
echo "ERROR: Acceso denegado a archivo sensible: $file_path" >&2
exit 2
fi
exit 0.claude/hooks/format-and-lint.sh:
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
# Solo procesar archivos TypeScript/JavaScript
if [[ "$file_path" =~ \.(ts|tsx|js|jsx)$ ]]; then
echo "Formateando: $file_path" >&2
npx prettier --write "$file_path" 2>&1
echo "Linting: $file_path" >&2
npx eslint --fix "$file_path" 2>&1 || true # No fallar si hay errores de lint
fi
exit 0.claude/hooks/run-tests.sh:
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
# Solo ejecutar tests para archivos en src/
if [[ "$file_path" =~ ^.*/src/.* ]] && [[ "$file_path" =~ \.(ts|tsx)$ ]]; then
echo "Ejecutando tests relacionados con: $file_path" >&2
npm test -- --findRelatedTests "$file_path" --passWithNoTests 2>&1 || true
fi
exit 0Mejores prácticas
1. Usar rutas absolutas
✅ Correcto:
{
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/script.sh"
}❌ Incorrecto:
{
"command": ".claude/hooks/script.sh"
}2. Siempre entrecomillar variables
✅ Correcto:
npx prettier --write "$file_path"❌ Incorrecto:
npx prettier --write $file_path # Falla con espacios3. Usar set -euo pipefail en scripts bash
#!/usr/bin/env bash
set -euo pipefail # Salir en errores, variables no definidas, fallos en pipes
# Tu código aquí4. Validar entrada
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path // empty')
# Verificar que no está vacío
if [[ -z "$file_path" ]]; then
echo "ERROR: file_path no disponible" >&2
exit 1
fi
# Verificar path traversal
if [[ "$file_path" == *".."* ]]; then
echo "ERROR: Path traversal detectado" >&2
exit 2
fi5. Logging a stderr, output a stdout
#!/usr/bin/env bash
# Mensajes informativos a stderr (se muestran en verbose)
echo "Procesando archivo..." >&2
# Output JSON o datos a stdout (se usa para decisiones)
echo '{"status": "ok"}'6. Usar timeouts apropiados
{
"hooks": [
{
"type": "command",
"command": "operación-rápida.sh",
"timeout": 10
},
{
"type": "command",
"command": "operación-lenta.sh",
"timeout": 300
}
]
}7. No fallar por errores menores
# Lint puede encontrar errores pero no debe bloquear
npx eslint --fix "$file_path" 2>&1 || true
# Tests pueden fallar pero no debe bloquear la edición
npm test -- --findRelatedTests "$file_path" 2>&1 || trueDebugging de hooks
Ver qué hooks se ejecutan
# Modo verbose: muestra todos los hooks y su output
claude --verbose
# O durante la sesión, presiona Ctrl+OLogs de hooks
# Agregar logging explícito
echo "Hook ejecutado: $(date)" >> /tmp/claude-hooks.log
jq '.' >> /tmp/claude-hooks-input.log # Guardar JSON de entradaProbar hooks manualmente
# Simular entrada JSON
echo '{"tool_input": {"file_path": "/ruta/test.ts"}}' | .claude/hooks/script.shVerificar permisos
# Los scripts deben ser ejecutables
chmod +x .claude/hooks/*.sh
# Verificar
ls -la .claude/hooks/Puntos clave
- Los hooks se configuran en
settings.jsonusando estructura JSON - Tres ubicaciones: global, proyecto, local
- Variables disponibles: entorno (
$CLAUDE_PROJECT_DIR) y JSON stdin - Códigos de salida: 0 (éxito), 2 (bloquear), otros (error)
- Timeouts configurables por hook (default: 600s)
- Es mejor usar scripts externos que comandos inline complejos
- Siempre validar entrada, entrecomillar variables y usar rutas absolutas
- Usa
set -euo pipefailen scripts bash - Logging a stderr, output de decisiones a stdout
- Modo verbose (
--verboseo Ctrl+O) para debugging
Recursos adicionales
- Documentación oficial de hooks
- Referencia de configuración
- Ejemplos de hooks
Próximo paso: 5.4 Casos de uso avanzados - Patrones y recetas para hooks complejos en escenarios reales.
5.4 Casos de uso avanzados
Objetivo
Explorar patrones avanzados de hooks para escenarios reales: automatización de commits, tests, notificaciones, multi-lenguaje, integración con herramientas externas y más.
Introducción
En este punto veremos implementaciones completas y funcionales de hooks para casos de uso reales. Cada ejemplo incluye código completo, configuración y explicación de las decisiones de diseño.
Caso de uso 1: Auto-commit después de cambios
Escenario: Quieres que Claude haga commit automáticamente después de cada edición, ideal para tener un historial granular de cambios.
Implementación
.claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/auto-commit.sh",
"timeout": 60
}
]
}
]
}
}.claude/hooks/auto-commit.sh:
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
tool_name=$(jq -r '.tool_name')
# Verificar que estamos en un repositorio git
if ! git rev-parse --git-dir > /dev/null 2>&1; then
echo "No es un repositorio git, omitiendo commit" >&2
exit 0
fi
# Verificar que el archivo existe
if [[ ! -f "$file_path" ]]; then
echo "Archivo no existe: $file_path" >&2
exit 0
fi
# Obtener el nombre del archivo relativo al repo
relative_path=$(git ls-files --full-name "$file_path" 2>/dev/null || basename "$file_path")
# Crear mensaje de commit descriptivo
commit_msg="Auto: $tool_name $relative_path
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
# Agregar y hacer commit
git add "$file_path"
git commit -m "$commit_msg" --quiet 2>&1 || {
echo "Commit falló o no hay cambios" >&2
exit 0
}
echo "✓ Commit automático realizado: $relative_path" >&2
exit 0Mejoras opcionales
Agregar SHA del commit al log:
commit_sha=$(git rev-parse --short HEAD)
echo "[$(date -Iseconds)] Commit $commit_sha: $relative_path" >> "$CLAUDE_PROJECT_DIR/.claude/commit-log.txt"Evitar commits vacíos:
# Verificar que hay cambios
if git diff --cached --quiet; then
echo "No hay cambios para commitear" >&2
exit 0
fiCaso de uso 2: Ejecutar tests relacionados inteligentemente
Escenario: Ejecutar solo los tests relacionados con el archivo modificado, no toda la suite de tests.
Implementación multi-framework
.claude/hooks/run-related-tests.sh:
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
# Solo procesar archivos de código fuente
if [[ ! "$file_path" =~ \.(ts|tsx|js|jsx|py|go)$ ]]; then
exit 0
fi
# No ejecutar tests para archivos de test
if [[ "$file_path" =~ \.(test|spec)\. ]]; then
echo "Archivo de test modificado, omitiendo ejecución" >&2
exit 0
fi
echo "Ejecutando tests relacionados con: $file_path" >&2
# Detectar el framework de testing
if [[ -f "package.json" ]] && grep -q '"jest"' package.json; then
# Jest (JavaScript/TypeScript)
npm test -- --findRelatedTests "$file_path" --passWithNoTests 2>&1 || {
echo "⚠ Algunos tests fallaron" >&2
exit 0 # No bloquear por tests fallidos
}
elif [[ -f "pytest.ini" ]] || [[ -f "setup.py" ]]; then
# Pytest (Python)
pytest "$file_path" --tb=short 2>&1 || {
echo "⚠ Algunos tests fallaron" >&2
exit 0
}
elif [[ "$file_path" =~ \.go$ ]]; then
# Go testing
package_dir=$(dirname "$file_path")
go test "./$package_dir" -v 2>&1 || {
echo "⚠ Algunos tests fallaron" >&2
exit 0
}
else
echo "Framework de testing no detectado" >&2
exit 0
fi
echo "✓ Tests completados" >&2
exit 0Variante: Solo tests unitarios rápidos
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
# Jest con timeout corto y solo tests unitarios
npm test -- \
--findRelatedTests "$file_path" \
--testPathIgnorePatterns="e2e|integration" \
--maxWorkers=2 \
--testTimeout=5000 \
--passWithNoTests \
2>&1 || exit 0
exit 0Caso de uso 3: Notificaciones de sistema avanzadas
Escenario: Notificaciones desktop con información contextual útil.
Implementación cross-platform
.claude/hooks/notify.sh:
#!/usr/bin/env bash
set -euo pipefail
# Detectar sistema operativo
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
NOTIFY_CMD="notify-send"
elif [[ "$OSTYPE" == "darwin"* ]]; then
NOTIFY_CMD="osascript -e"
else
echo "Sistema no soportado para notificaciones" >&2
exit 0
fi
# Función para enviar notificación
send_notification() {
local title="$1"
local message="$2"
local urgency="${3:-normal}"
if [[ "$NOTIFY_CMD" == "notify-send" ]]; then
notify-send -u "$urgency" "$title" "$message"
else
osascript -e "display notification \"$message\" with title \"$title\""
fi
}
# Hook Stop: Claude terminó
if [[ "${HOOK_EVENT:-}" == "Stop" ]]; then
# Calcular duración desde SessionStart
if [[ -f "$CLAUDE_PROJECT_DIR/.claude/session-start" ]]; then
start_time=$(cat "$CLAUDE_PROJECT_DIR/.claude/session-start")
duration=$(($(date +%s) - start_time))
send_notification "Claude Code" "Tarea completada en ${duration}s" "normal"
else
send_notification "Claude Code" "Claude ha terminado" "normal"
fi
fi
exit 0Notificación con preview de cambios
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
tool_name=$(jq -r '.tool_name')
# Obtener estadísticas del archivo
if [[ -f "$file_path" ]]; then
lines=$(wc -l < "$file_path")
size=$(du -h "$file_path" | cut -f1)
notify-send "Claude Code: $tool_name" \
"Archivo: $(basename "$file_path")\nLíneas: $lines\nTamaño: $size" \
--icon=dialog-information
fi
exit 0Caso de uso 4: Formateo y linting multi-lenguaje
Escenario: Formatear y lintear automáticamente según el lenguaje del archivo.
Implementación completa
.claude/hooks/format-multi-lang.sh:
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
extension="${file_path##*.}"
echo "Procesando: $file_path (.$extension)" >&2
case "$extension" in
ts|tsx|js|jsx)
echo "→ Prettier + ESLint" >&2
npx prettier --write "$file_path" 2>&1
npx eslint --fix "$file_path" 2>&1 || true
;;
py)
echo "→ Black + isort + Ruff" >&2
python -m black "$file_path" 2>&1 || true
python -m isort "$file_path" 2>&1 || true
python -m ruff check --fix "$file_path" 2>&1 || true
;;
go)
echo "→ gofmt + goimports" >&2
gofmt -w "$file_path" 2>&1
goimports -w "$file_path" 2>&1 || true
;;
rs)
echo "→ rustfmt" >&2
rustfmt "$file_path" 2>&1 || true
;;
java)
echo "→ google-java-format" >&2
google-java-format --replace "$file_path" 2>&1 || true
;;
md)
echo "→ Prettier (Markdown)" >&2
npx prettier --write "$file_path" 2>&1 || true
;;
json|yaml|yml)
echo "→ Prettier (config files)" >&2
npx prettier --write "$file_path" 2>&1 || true
;;
*)
echo "No hay formateador configurado para .$extension" >&2
;;
esac
echo "✓ Formateo completado" >&2
exit 0Configuración con cache
.claude/hooks/format-with-cache.sh:
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
cache_dir="$CLAUDE_PROJECT_DIR/.claude/cache"
mkdir -p "$cache_dir"
# Calcular hash del archivo
file_hash=$(md5sum "$file_path" | cut -d' ' -f1)
cache_file="$cache_dir/$(basename "$file_path").$file_hash"
# Si ya formateamos este contenido, skip
if [[ -f "$cache_file" ]]; then
echo "Cache hit, omitiendo formateo" >&2
exit 0
fi
# Formatear
if [[ "$file_path" =~ \.(ts|tsx|js|jsx)$ ]]; then
npx prettier --write "$file_path" 2>&1
fi
# Marcar como formateado
touch "$cache_file"
# Limpiar cache viejo (más de 7 días)
find "$cache_dir" -type f -mtime +7 -delete 2>/dev/null || true
exit 0Caso de uso 5: Integración con Slack/Discord
Escenario: Notificar al equipo cuando Claude complete tareas importantes.
Webhook de Slack
.claude/hooks/notify-slack.sh:
#!/usr/bin/env bash
set -euo pipefail
# Configurar webhook URL (usar settings.local.json para no commitear)
SLACK_WEBHOOK_URL="${SLACK_WEBHOOK_URL:-}"
if [[ -z "$SLACK_WEBHOOK_URL" ]]; then
echo "SLACK_WEBHOOK_URL no configurado" >&2
exit 0
fi
# Obtener información del contexto
session_id=$(jq -r '.session_id // "unknown"')
project_name=$(basename "$CLAUDE_PROJECT_DIR")
user=$(whoami)
branch=$(git branch --show-current 2>/dev/null || echo "no-git")
# Construir mensaje
message="Claude Code completó tarea en *$project_name*
• Usuario: $user
• Branch: $branch
• Session: ${session_id:0:8}"
# Enviar a Slack
curl -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"text\":\"$message\"}" \
--silent --show-error \
2>&1 || {
echo "Fallo al enviar a Slack" >&2
exit 0
}
echo "✓ Notificación enviada a Slack" >&2
exit 0.claude/settings.local.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "SLACK_WEBHOOK_URL='https://hooks.slack.com/services/YOUR/WEBHOOK/URL' .claude/hooks/notify-slack.sh",
"timeout": 30
}
]
}
]
}
}Webhook de Discord
.claude/hooks/notify-discord.sh:
#!/usr/bin/env bash
set -euo pipefail
DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-}"
if [[ -z "$DISCORD_WEBHOOK_URL" ]]; then
exit 0
fi
project_name=$(basename "$CLAUDE_PROJECT_DIR")
branch=$(git branch --show-current 2>/dev/null || echo "no-git")
# Discord usa formato JSON diferente
payload=$(cat <<EOF
{
"embeds": [{
"title": "Claude Code: Tarea completada",
"description": "Proyecto: **$project_name**\nBranch: \`$branch\`",
"color": 5814783,
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)"
}]
}
EOF
)
curl -X POST "$DISCORD_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "$payload" \
--silent 2>&1 || exit 0
exit 0Caso de uso 6: Pre-commit validation
Escenario: Validar que el código cumple estándares antes de permitir commits.
.claude/hooks/pre-commit-validation.sh:
#!/usr/bin/env bash
set -euo pipefail
command=$(jq -r '.tool_input.command // empty')
# Solo validar comandos git commit
if [[ ! "$command" =~ ^git[[:space:]]+commit ]]; then
exit 0
fi
echo "Validando código antes de commit..." >&2
# 1. Verificar que no hay archivos sensibles staged
sensitive_files=$(git diff --cached --name-only | grep -E '\.(env|pem|key|p12)$' || true)
if [[ -n "$sensitive_files" ]]; then
echo "ERROR: Archivos sensibles detectados en stage:" >&2
echo "$sensitive_files" >&2
exit 2 # Bloquear commit
fi
# 2. Ejecutar linters en archivos staged
echo "Ejecutando linters..." >&2
staged_files=$(git diff --cached --name-only --diff-filter=ACM)
for file in $staged_files; do
if [[ -f "$file" ]]; then
case "$file" in
*.ts|*.tsx|*.js|*.jsx)
npx eslint "$file" 2>&1 || {
echo "ERROR: ESLint falló en $file" >&2
exit 2
}
;;
*.py)
python -m ruff check "$file" 2>&1 || {
echo "ERROR: Ruff falló en $file" >&2
exit 2
}
;;
esac
fi
done
# 3. Ejecutar tests
echo "Ejecutando tests..." >&2
npm test -- --passWithNoTests --bail 2>&1 || {
echo "ERROR: Tests fallaron" >&2
exit 2
}
echo "✓ Validación completada" >&2
exit 0Caso de uso 7: Code coverage tracking
Escenario: Monitorear la cobertura de código y alertar si baja.
.claude/hooks/track-coverage.sh:
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
coverage_dir="$CLAUDE_PROJECT_DIR/.claude/coverage"
mkdir -p "$coverage_dir"
# Solo para archivos de código
if [[ ! "$file_path" =~ \.(ts|tsx|js|jsx)$ ]]; then
exit 0
fi
# Ejecutar tests con coverage
echo "Calculando cobertura..." >&2
npm test -- \
--coverage \
--coverageReporters=json-summary \
--collectCoverageFrom="$file_path" \
--passWithNoTests \
> /dev/null 2>&1 || exit 0
# Leer cobertura
if [[ -f "coverage/coverage-summary.json" ]]; then
coverage=$(jq -r '.total.lines.pct' coverage/coverage-summary.json 2>/dev/null || echo "0")
# Guardar historial
echo "$(date -Iseconds),$file_path,$coverage" >> "$coverage_dir/history.csv"
# Alertar si baja de 80%
if (( $(echo "$coverage < 80" | bc -l) )); then
echo "⚠ Cobertura baja: ${coverage}% en $file_path" >&2
notify-send "Claude Code: Cobertura baja" \
"Archivo: $(basename "$file_path")\nCobertura: ${coverage}%" \
--urgency=critical 2>/dev/null || true
else
echo "✓ Cobertura: ${coverage}%" >&2
fi
fi
exit 0Caso de uso 8: Documentación automática
Escenario: Generar/actualizar documentación cuando se modifican archivos.
.claude/hooks/auto-document.sh:
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
# Solo documentar archivos en src/
if [[ ! "$file_path" =~ ^.*/src/.* ]]; then
exit 0
fi
# Solo para archivos de código
if [[ ! "$file_path" =~ \.(ts|tsx|js|jsx|py)$ ]]; then
exit 0
fi
# Generar documentación con typedoc o pydoc
if [[ "$file_path" =~ \.(ts|tsx)$ ]] && command -v typedoc > /dev/null; then
echo "Generando documentación TypeScript..." >&2
npx typedoc --entryPoints "$file_path" --out docs/api --skipErrorChecking 2>&1 || true
elif [[ "$file_path" =~ \.py$ ]] && command -v pydoc > /dev/null; then
echo "Generando documentación Python..." >&2
module_name=$(basename "$file_path" .py)
pydoc -w "$module_name" 2>&1 || true
fi
# Actualizar README con TOC
if [[ -f "README.md" ]]; then
echo "Actualizando tabla de contenidos..." >&2
npx markdown-toc -i README.md 2>&1 || true
fi
exit 0Caso de uso 9: Security scanning
Escenario: Escanear archivos modificados en busca de vulnerabilidades.
.claude/hooks/security-scan.sh:
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
echo "Escaneando seguridad: $file_path" >&2
# 1. Buscar secretos hardcodeados
if command -v trufflehog > /dev/null; then
trufflehog filesystem "$file_path" --json 2>&1 | \
jq -r 'select(.Verified == true) | "⚠ SECRET ENCONTRADO: \(.DetectorName)"' || true
fi
# 2. Escanear vulnerabilidades conocidas
case "$file_path" in
*.py)
if command -v bandit > /dev/null; then
bandit -r "$file_path" -f json 2>&1 | \
jq -r '.results[] | select(.issue_severity == "HIGH" or .issue_severity == "CRITICAL") |
"⚠ \(.issue_severity): \(.issue_text)"' || true
fi
;;
*.js|*.ts)
if [[ -f "package.json" ]]; then
npm audit --json 2>&1 | \
jq -r '.vulnerabilities | to_entries[] | select(.value.severity == "high" or .value.severity == "critical") |
"⚠ \(.value.severity): \(.key)"' || true
fi
;;
esac
# 3. Verificar que no hay eval() o exec()
if grep -nE '(eval\(|exec\(|system\(|shell_exec)' "$file_path" 2>/dev/null; then
echo "⚠ Funciones peligrosas detectadas en $file_path" >&2
fi
exit 0Caso de uso 10: Performance monitoring
Escenario: Monitorear el rendimiento de hooks y alertar si son muy lentos.
.claude/hooks/performance-wrapper.sh:
#!/usr/bin/env bash
set -euo pipefail
# Este wrapper ejecuta otro hook y mide su tiempo
TARGET_HOOK="${1:-}"
THRESHOLD_SECONDS="${2:-5}"
if [[ -z "$TARGET_HOOK" ]]; then
echo "Uso: performance-wrapper.sh <hook-script> [threshold-seconds]" >&2
exit 1
fi
# Leer JSON de stdin y guardarlo
json_input=$(cat)
# Ejecutar hook con timing
start=$(date +%s.%N)
echo "$json_input" | "$TARGET_HOOK"
exit_code=$?
end=$(date +%s.%N)
# Calcular duración
duration=$(echo "$end - $start" | bc)
# Registrar performance
perf_log="$CLAUDE_PROJECT_DIR/.claude/performance.log"
echo "$(date -Iseconds),$TARGET_HOOK,$duration,$exit_code" >> "$perf_log"
# Alertar si excede el threshold
if (( $(echo "$duration > $THRESHOLD_SECONDS" | bc -l) )); then
echo "⚠ Hook lento detectado: $TARGET_HOOK tomó ${duration}s" >&2
notify-send "Claude Code: Hook lento" \
"Hook: $(basename "$TARGET_HOOK")\nDuración: ${duration}s" \
--urgency=normal 2>/dev/null || true
fi
exit $exit_codeUso en settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/performance-wrapper.sh .claude/hooks/format.sh 3",
"timeout": 90
}
]
}
]
}
}Caso de uso 11: Hooks con estado persistente
Escenario: Mantener estado entre ejecuciones de hooks (contadores, estadísticas).
.claude/hooks/track-changes.sh:
#!/usr/bin/env bash
set -euo pipefail
state_file="$CLAUDE_PROJECT_DIR/.claude/state.json"
# Inicializar estado si no existe
if [[ ! -f "$state_file" ]]; then
echo '{"files_modified": 0, "files_created": 0, "total_lines": 0}' > "$state_file"
fi
# Leer estado actual
files_modified=$(jq -r '.files_modified' "$state_file")
files_created=$(jq -r '.files_created' "$state_file")
total_lines=$(jq -r '.total_lines' "$state_file")
# Procesar evento actual
file_path=$(jq -r '.tool_input.file_path')
tool_name=$(jq -r '.tool_name')
if [[ "$tool_name" == "Write" ]] && [[ ! -f "$file_path" ]]; then
files_created=$((files_created + 1))
elif [[ "$tool_name" == "Edit" ]]; then
files_modified=$((files_modified + 1))
fi
# Contar líneas
if [[ -f "$file_path" ]]; then
lines=$(wc -l < "$file_path")
total_lines=$((total_lines + lines))
fi
# Guardar estado actualizado
jq -n \
--arg fm "$files_modified" \
--arg fc "$files_created" \
--arg tl "$total_lines" \
'{files_modified: ($fm|tonumber), files_created: ($fc|tonumber), total_lines: ($tl|tonumber)}' \
> "$state_file"
echo "📊 Estadísticas: $files_modified editados, $files_created creados, $total_lines líneas totales" >&2
exit 0Ver estadísticas:
cat .claude/state.json | jqCaso de uso 12: Hooks condicionales
Escenario: Ejecutar hooks solo en ciertos contextos (branches, directorios, horarios).
.claude/hooks/conditional-tests.sh:
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
# Condición 1: Solo ejecutar en branch main/master
current_branch=$(git branch --show-current 2>/dev/null || echo "unknown")
if [[ ! "$current_branch" =~ ^(main|master)$ ]]; then
echo "No estás en main/master, omitiendo tests exhaustivos" >&2
exit 0
fi
# Condición 2: Solo archivos críticos
if [[ ! "$file_path" =~ (src/core|src/auth|src/payment) ]]; then
echo "No es código crítico, omitiendo tests exhaustivos" >&2
exit 0
fi
# Condición 3: Solo durante horario laboral
hour=$(date +%H)
if (( hour < 9 || hour > 18 )); then
echo "Fuera de horario laboral, omitiendo tests lentos" >&2
exit 0
fi
# Ejecutar tests completos
echo "Ejecutando suite completa de tests..." >&2
npm test -- --coverage 2>&1 || exit 0
exit 0Mejores prácticas para casos avanzados
1. Manejo de errores robusto
#!/usr/bin/env bash
set -euo pipefail
# Trap para cleanup
cleanup() {
local exit_code=$?
echo "Limpiando recursos..." >&2
# Cleanup aquí
exit $exit_code
}
trap cleanup EXIT
# Tu código aquí2. Timeouts internos
# Ejecutar comando con timeout interno
timeout 30s npm test || {
echo "Timeout alcanzado" >&2
exit 0
}3. Logging estructurado
log() {
local level="$1"
shift
echo "$(date -Iseconds) [$level] $*" >> "$CLAUDE_PROJECT_DIR/.claude/hooks.log"
}
log INFO "Iniciando hook"
log ERROR "Algo falló"4. Testing de hooks
# test-hook.sh
#!/usr/bin/env bash
# Simular entrada JSON
test_input='{"tool_input": {"file_path": "/tmp/test.ts"}, "tool_name": "Write"}'
# Ejecutar hook
echo "$test_input" | .claude/hooks/your-hook.sh
# Verificar resultado
if [[ $? -eq 0 ]]; then
echo "✓ Test pasó"
else
echo "✗ Test falló"
exit 1
fiPlantilla de hook avanzado
Usa esta plantilla como punto de partida:
#!/usr/bin/env bash
set -euo pipefail
# =============================================================================
# Hook: [Nombre del hook]
# Descripción: [Qué hace este hook]
# =============================================================================
# --- Configuración ---
TIMEOUT=30
LOG_FILE="$CLAUDE_PROJECT_DIR/.claude/hook-name.log"
# --- Funciones auxiliares ---
log() {
echo "$(date -Iseconds) [$1] ${*:2}" | tee -a "$LOG_FILE" >&2
}
cleanup() {
log INFO "Limpiando recursos"
# Cleanup aquí
}
trap cleanup EXIT
# --- Validación de entrada ---
if ! jq -e . >/dev/null 2>&1; then
log ERROR "JSON de entrada inválido"
exit 1
fi
# --- Extracción de datos ---
file_path=$(jq -r '.tool_input.file_path // empty')
tool_name=$(jq -r '.tool_name // empty')
if [[ -z "$file_path" ]]; then
log WARN "file_path no disponible"
exit 0
fi
# --- Validaciones condicionales ---
if [[ ! -f "$file_path" ]]; then
log WARN "Archivo no existe: $file_path"
exit 0
fi
# Solo procesar ciertos tipos de archivos
if [[ ! "$file_path" =~ \.(ts|tsx|js|jsx)$ ]]; then
log INFO "Tipo de archivo no soportado: $file_path"
exit 0
fi
# --- Lógica principal ---
log INFO "Procesando: $file_path"
# Tu código aquí
# ...
log INFO "Completado exitosamente"
exit 0Depuración de hooks avanzados
Ver logs de hooks
# Ver últimos logs
tail -f .claude/hooks.log
# Ver solo errores
grep ERROR .claude/hooks.log
# Ver estadísticas de performance
sort -t, -k3 -n .claude/performance.log | tail -10Probar hooks individualmente
# Crear JSON de prueba
cat > /tmp/test-input.json <<EOF
{
"tool_input": {
"file_path": "/ruta/a/archivo.ts",
"command": "npm test"
},
"tool_name": "Write",
"session_id": "test-session"
}
EOF
# Ejecutar hook
cat /tmp/test-input.json | .claude/hooks/your-hook.shPuntos clave
- Los hooks avanzados pueden integrarse con cualquier herramienta externa
- Usa JSON de entrada/salida para comunicación estructurada
- Implementa manejo de errores robusto con traps y cleanup
- Registra métricas (performance, contadores, logs)
- Hooks condicionales ahorran tiempo en contextos específicos
- Testing de hooks es fundamental para reliability
- Usa timeouts y validaciones para evitar bloqueos
- Documenta cada hook con su propósito y casos de uso
- Mantén estado persistente cuando sea necesario
- Monitorea performance de hooks para optimizar
Recursos adicionales
- Documentación oficial de hooks
- Ejemplos de la comunidad
- Biblioteca de hooks reutilizables
Próximo paso: 5.5 Timeout de hooks - Configuración, mejores prácticas y manejo de hooks lentos.
5.5 Timeout de hooks
Objetivo
Comprender cómo funcionan los timeouts en hooks, configurarlos apropiadamente, optimizar hooks lentos y evitar que bloqueen tu flujo de trabajo.
Introducción
Los hooks son poderosos, pero si son demasiado lentos pueden hacer que trabajar con Claude Code se sienta lento y frustrante. Los timeouts son el mecanismo de seguridad que evita que hooks problemáticos bloqueen indefinidamente tu sesión.
En este punto aprenderás a configurar timeouts apropiados, identificar hooks lentos y optimizar su rendimiento.
¿Qué es un timeout de hook?
Un timeout es el tiempo máximo que Claude Code esperará a que un hook termine su ejecución antes de matarlo forzosamente.
Comportamiento del timeout
Hook inicia ejecución
↓
Pasan 10 segundos
↓
Pasan 30 segundos
↓
Pasan 60 segundos (timeout alcanzado)
↓
Hook es terminado con SIGTERM
↓
Si no responde en 5 segundos → SIGKILL
↓
Claude Code continúaResultado: El hook es terminado, se registra el timeout, y la sesión continúa normalmente.
Configuración de timeouts
Valor por defecto
Sin configuración explícita: 600 segundos (10 minutos)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "./script.sh"
// timeout: 600 (default implícito)
}
]
}
]
}
}Configuración explícita
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "./script.sh",
"timeout": 30 // 30 segundos
}
]
}
]
}
}Unidades: Segundos (entero positivo)
Guía de timeouts recomendados
Basado en la operación que realiza el hook:
| Tipo de operación | Timeout recomendado | Justificación |
|---|---|---|
| Formateo (Prettier, Black) | 30-60s | Operación rápida en archivos individuales |
| Linting (ESLint, Pylint) | 60-120s | Puede analizar dependencias |
| Tests unitarios | 120-300s | Depende del número de tests |
| Tests de integración | 300-600s | Pueden involucrar DB, red, etc. |
| Compilación | 180-600s | Proyectos grandes tardan más |
| Operaciones de red | 60-180s | APIs externas, webhooks |
| Logging/auditoría | 10-30s | Operaciones I/O simples |
| Notificaciones | 5-15s | Deben ser instantáneas |
| Security scans | 120-300s | Análisis profundo de código |
Regla general
Si tu hook tarda más de 2 minutos regularmente, probablemente necesita optimización o debería ejecutarse de forma diferente.
Síntomas de hooks lentos
Cómo detectar que tienes un problema
Síntomas en la sesión:
- Claude Code se siente "trabado" después de ediciones
- Hay pausas largas antes de que Claude responda
- Ves mensajes de "Hook ejecutándose..." por mucho tiempo
Síntomas técnicos:
- Hooks alcanzan su timeout frecuentemente
- Logs muestran duraciones >60 segundos
- CPU usage alto durante minutos después de ediciones
Verificar performance de hooks
Ver hooks en ejecución:
# Durante sesión, presiona Ctrl+O para modo verbose
# Verás output de hooks en tiempo realRevisar logs de performance (si implementaste logging):
# Ver hooks más lentos
cat .claude/performance.log | sort -t, -k3 -rn | head -10
# Ver promedio de duración
awk -F, '{sum+=$3; count++} END {print "Promedio:", sum/count "s"}' .claude/performance.logEstrategias de optimización
1. Operaciones paralelas → secuenciales
❌ Problema: Ejecutar múltiples hooks pesados en cada edición
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{"type": "command", "command": "npm run format", "timeout": 60},
{"type": "command", "command": "npm run lint", "timeout": 120},
{"type": "command", "command": "npm test", "timeout": 300},
{"type": "command", "command": "npm run build", "timeout": 600}
]
}
]
}
}Total potencial: Hasta 1080 segundos (18 minutos) por edición
✅ Solución: Priorizar y ejecutar solo lo esencial
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{"type": "command", "command": "./quick-format.sh", "timeout": 30}
// Tests y build se ejecutan manualmente o en CI/CD
]
}
]
}
}2. Filtrado inteligente
❌ Problema: Hook ejecuta en todos los archivos
#!/usr/bin/env bash
# Ejecuta prettier en TODO el proyecto
npx prettier --write .✅ Solución: Procesar solo el archivo modificado
#!/usr/bin/env bash
file_path=$(jq -r '.tool_input.file_path')
# Solo formatear archivos relevantes
if [[ "$file_path" =~ \.(ts|tsx|js|jsx)$ ]]; then
npx prettier --write "$file_path"
fi3. Cache para evitar trabajo duplicado
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
cache_dir="$CLAUDE_PROJECT_DIR/.claude/cache"
mkdir -p "$cache_dir"
# Hash del contenido
content_hash=$(md5sum "$file_path" | cut -d' ' -f1)
cache_key="$cache_dir/$(basename "$file_path").$content_hash"
# Si ya procesamos este contenido, skip
if [[ -f "$cache_key" ]]; then
echo "Cache hit, skipping" >&2
exit 0
fi
# Hacer el trabajo
npx prettier --write "$file_path"
# Marcar como procesado
touch "$cache_key"
# Limpiar cache viejo (>7 días)
find "$cache_dir" -type f -mtime +7 -delete 2>/dev/null || true
exit 0Resultado: Primera ejecución tarda normal, siguientes son instantáneas.
4. Skip en archivos no modificados
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
# Verificar si el archivo realmente cambió
if git diff --quiet "$file_path" 2>/dev/null; then
echo "Archivo sin cambios, skipping tests" >&2
exit 0
fi
# Ejecutar tests
npm test -- --findRelatedTests "$file_path"5. Límites de ejecución
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
# Solo ejecutar tests unitarios (rápidos)
npm test -- \
--findRelatedTests "$file_path" \
--testPathIgnorePatterns="e2e|integration" \
--maxWorkers=2 \
--testTimeout=5000 \
--passWithNoTests
exit 06. Background execution
Para operaciones realmente pesadas, usa background:
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
project_dir="$CLAUDE_PROJECT_DIR"
# Ejecutar tests completos en background
(
cd "$project_dir"
npm test -- --coverage > .claude/test-results.log 2>&1
# Notificar cuando termine
notify-send "Tests completados" "Revisa .claude/test-results.log"
) &
# Hook termina inmediatamente
echo "Tests iniciados en background" >&2
exit 0Pros: No bloquea la sesión Cons: No sabes resultado inmediatamente
Hooks condicionales para ahorrar tiempo
Solo ejecutar en ciertos contextos
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
# Condición 1: Branch
branch=$(git branch --show-current 2>/dev/null || echo "unknown")
if [[ ! "$branch" =~ ^(main|master|develop)$ ]]; then
echo "Not on main branch, skipping expensive checks" >&2
exit 0
fi
# Condición 2: Tamaño de archivo
size=$(wc -l < "$file_path")
if (( size > 1000 )); then
echo "File too large, skipping linting" >&2
exit 0
fi
# Condición 3: Tipo de archivo crítico
if [[ ! "$file_path" =~ (src/core|src/auth) ]]; then
echo "Not critical code, skipping full test suite" >&2
exit 0
fi
# Ejecutar checks completos solo si cumple todas las condiciones
npm run lint
npm test -- --coverageManejo de timeouts alcanzados
Qué sucede cuando un hook alcanza el timeout
- Claude Code envía SIGTERM al proceso del hook
- El hook tiene ~5 segundos para terminar limpiamente
- Si no termina, Claude Code envía SIGKILL (forzoso)
- El timeout se registra en logs
- La sesión continúa normalmente
Implementar cleanup al recibir señales
#!/usr/bin/env bash
set -euo pipefail
# Trap para manejar SIGTERM (timeout)
cleanup() {
local exit_code=$?
echo "Hook interrumpido (timeout o señal), limpiando..." >&2
# Limpiar archivos temporales
rm -f /tmp/hook-$$-*
# Matar procesos hijos
pkill -P $$ 2>/dev/null || true
exit $exit_code
}
trap cleanup EXIT SIGTERM SIGINT
# Tu código aquí
echo "Ejecutando operación..." >&2
npm test
exit 0Configuración por tipo de proyecto
Proyecto pequeño (< 10k líneas)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{"type": "command", "command": ".claude/hooks/format.sh", "timeout": 30},
{"type": "command", "command": ".claude/hooks/lint.sh", "timeout": 60},
{"type": "command", "command": ".claude/hooks/test.sh", "timeout": 120}
]
}
]
}
}Justificación: Proyecto pequeño, operaciones rápidas, tests completos.
Proyecto mediano (10k-100k líneas)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{"type": "command", "command": ".claude/hooks/format.sh", "timeout": 45},
{"type": "command", "command": ".claude/hooks/lint-file.sh", "timeout": 90},
{"type": "command", "command": ".claude/hooks/test-related.sh", "timeout": 180}
]
}
]
}
}Justificación: Solo lintear archivo modificado, solo tests relacionados.
Proyecto grande (> 100k líneas)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{"type": "command", "command": ".claude/hooks/format-cached.sh", "timeout": 30}
// Linting y tests se ejecutan en CI/CD, no en hooks
]
}
]
}
}Justificación: Solo formateo con cache, todo lo demás en CI/CD.
Debugging de problemas de timeout
Paso 1: Identificar el hook problemático
# Activar modo verbose
claude --verbose
# O durante sesión: Ctrl+O
# Verás output como:
# [Hook PostToolUse] Iniciando: .claude/hooks/test.sh
# [Hook PostToolUse] Duración: 145.2s
# [Hook PostToolUse] Timeout alcanzado: .claude/hooks/test.shPaso 2: Medir manualmente
# Simular entrada del hook
cat > /tmp/test-input.json <<EOF
{
"tool_input": {"file_path": "/ruta/a/archivo.ts"},
"tool_name": "Write"
}
EOF
# Ejecutar con timing
time cat /tmp/test-input.json | .claude/hooks/test.sh
# Output:
# real 2m15.432s ← Duración real
# user 1m45.234s
# sys 0m5.123sPaso 3: Perfilar el hook
Añadir logging de timing interno:
#!/usr/bin/env bash
set -euo pipefail
log_time() {
echo "[$(date +%H:%M:%S.%3N)] $*" >&2
}
log_time "Hook iniciado"
file_path=$(jq -r '.tool_input.file_path')
log_time "JSON parseado"
log_time "Iniciando formateo"
npx prettier --write "$file_path"
log_time "Formateo completado"
log_time "Iniciando linting"
npx eslint --fix "$file_path"
log_time "Linting completado"
log_time "Hook completado"
exit 0Output en modo verbose:
[14:23:45.123] Hook iniciado
[14:23:45.234] JSON parseado
[14:23:45.235] Iniciando formateo
[14:23:48.456] Formateo completado
[14:23:48.457] Iniciando linting
[14:24:32.789] Linting completado
[14:24:32.790] Hook completadoConclusión: El linting tarda 44 segundos, es el cuello de botella.
Paso 4: Optimizar la operación lenta
# Antes: Lint de todo el proyecto
npx eslint --fix .
# Después: Solo el archivo modificado
npx eslint --fix "$file_path"
# Ahora tarda: 2-3 segundosMejores prácticas de timeout
1. Empezar conservador, ajustar con datos
// Primera versión: timeout generoso
{"timeout": 300}
// Después de medir: promedio 45s, máximo 80s
{"timeout": 120} // 50% buffer sobre máximo observado2. Timeouts diferentes por operación
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{"command": "./format.sh", "timeout": 30}, // Rápido
{"command": "./lint.sh", "timeout": 90}, // Medio
{"command": "./test.sh", "timeout": 180} // Lento
]
}
]
}
}3. Alertar antes de timeout
#!/usr/bin/env bash
set -euo pipefail
TIMEOUT=120
WARN_AT=90
# Ejecutar comando en background con monitoreo
(
sleep $WARN_AT
echo "⚠ Hook llevando mucho tiempo (${WARN_AT}s)" >&2
) &
warn_pid=$!
# Ejecutar operación principal
npm test
# Cancelar warning si terminamos a tiempo
kill $warn_pid 2>/dev/null || true
exit 04. Implementar timeout interno
#!/usr/bin/env bash
set -euo pipefail
# Timeout interno más agresivo que el de Claude Code
timeout 90s npm test || {
echo "Timeout interno alcanzado, abortando" >&2
exit 0 # No fallar, solo skip
}
exit 05. Degradación graceful
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
# Intentar tests completos con timeout corto
if timeout 60s npm test -- --findRelatedTests "$file_path" 2>/dev/null; then
echo "✓ Tests completos pasaron" >&2
else
# Fallback: solo smoke tests
echo "Timeout en tests completos, ejecutando smoke tests" >&2
timeout 15s npm test -- --testNamePattern="smoke" 2>/dev/null || true
fi
exit 0Checklist de optimización
Usa esta lista para revisar hooks lentos:
- El hook procesa solo el archivo modificado, no todo el proyecto
- Tiene cache para evitar trabajo duplicado
- Usa filtros para skip de archivos irrelevantes
- Implementa cleanup al recibir señales (SIGTERM)
- Tiene logging de timing para identificar cuellos de botella
- El timeout es apropiado para la operación (ni muy corto, ni muy largo)
- Operaciones muy lentas están en background o en CI/CD
- Hay condiciones para skip en contextos no críticos
- No ejecuta tests completos en cada edición
- No compila todo el proyecto en cada edición
Cuándo mover operaciones a CI/CD
Si tu hook cumple alguno de estos criterios, considera moverlo a CI/CD:
- Tarda más de 3 minutos regularmente
- Requiere recursos significativos (CPU, memoria, red)
- Solo necesita ejecutarse en código finalizado, no durante desarrollo
- Ejecuta tests de integración o E2E
- Compila todo el proyecto
- Hace operaciones de red (deploy, notificaciones externas complejas)
- Genera reportes o artefactos pesados
Regla de oro: Los hooks deben ser rápidos y específicos. Si es lento y global, va en CI/CD.
Ejemplo completo: Hooks optimizados
.claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/quick-format.sh",
"timeout": 30
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": ".claude/hooks/background-checks.sh",
"timeout": 5
}
]
}
]
}
}.claude/hooks/quick-format.sh (optimizado):
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
# Cache
cache_dir="$CLAUDE_PROJECT_DIR/.claude/cache"
mkdir -p "$cache_dir"
content_hash=$(md5sum "$file_path" | cut -d' ' -f1)
cache_key="$cache_dir/format.$(basename "$file_path").$content_hash"
if [[ -f "$cache_key" ]]; then
exit 0
fi
# Solo formatear tipos soportados
case "$file_path" in
*.ts|*.tsx|*.js|*.jsx)
timeout 25s npx prettier --write "$file_path" 2>&1 || true
;;
*.py)
timeout 25s python -m black "$file_path" 2>&1 || true
;;
esac
touch "$cache_key"
find "$cache_dir" -type f -mtime +7 -delete 2>/dev/null || true
exit 0.claude/hooks/background-checks.sh:
#!/usr/bin/env bash
# Ejecutar checks pesados en background (no bloquea sesión)
(
cd "$CLAUDE_PROJECT_DIR"
npm test -- --coverage > .claude/test-results.log 2>&1
npm run lint > .claude/lint-results.log 2>&1
# Notificar
notify-send "Claude Code" "Checks completos disponibles en .claude/"
) &
echo "Checks iniciados en background" >&2
exit 0Puntos clave
- Timeout por defecto: 600 segundos (10 minutos)
- Configura timeouts apropiados según la operación (30s-600s)
- Optimiza hooks lentos: cache, filtros, solo archivo modificado
- No ejecutes operaciones globales en hooks (tests completos, builds)
- Usa background execution para operaciones muy pesadas
- Implementa cleanup al recibir SIGTERM
- Mide performance con logging y ajusta timeouts con datos reales
- Degrada gracefully si operaciones tardan mucho
- Mueve a CI/CD lo que tarde >3 minutos o sea global
- Hooks deben ser rápidos y específicos, no lentos y globales
Recursos adicionales
- Documentación oficial de hooks
- Optimización de performance
- CI/CD integration guide
Felicidades, has completado el Módulo 5: Hooks
Has aprendido:
- ✅ Qué son los hooks y cuándo usarlos
- ✅ Todos los tipos de hooks disponibles
- ✅ Configuración en settings.json
- ✅ Casos de uso avanzados con código completo
- ✅ Optimización de performance y timeouts
Próximo paso: Módulo 6: MCP (Model Context Protocol) - Conecta Claude con herramientas y servicios externos.
Práctica del Módulo 5: Hooks
Objetivo
Aplicar todo lo aprendido sobre hooks configurando un sistema completo de automatización para un proyecto real.
Descripción
En esta práctica crearás una configuración completa de hooks para un proyecto de ejemplo, implementando:
- Formateo automático
- Linting
- Tests relacionados
- Logging y auditoría
- Notificaciones
- Seguridad
Prerequisitos
- Claude Code instalado y configurado
- Node.js 18+ (para proyecto de ejemplo)
- Git configurado
- Herramientas opcionales: notify-send (Linux) o similar
Proyecto de ejemplo
Usaremos un proyecto TypeScript/React simple. Si tienes un proyecto propio, puedes adaptar los ejercicios.
Setup del proyecto de ejemplo
# Crear proyecto
mkdir claude-hooks-practice
cd claude-hooks-practice
# Inicializar
npm init -y
git init
# Instalar dependencias
npm install --save-dev \
typescript \
prettier \
eslint \
@typescript-eslint/parser \
@typescript-eslint/eslint-plugin \
jest
# Crear estructura básica
mkdir -p src .claude/hookssrc/calculator.ts:
export function add(a: number, b: number): number {
return a + b;
}
export function multiply(a: number, b: number): number {
return a * b;
}src/calculator.test.ts:
import { add, multiply } from './calculator';
describe('Calculator', () => {
test('add sums two numbers', () => {
expect(add(2, 3)).toBe(5);
});
test('multiply multiplies two numbers', () => {
expect(multiply(3, 4)).toBe(12);
});
});Ejercicios
Ejercicio 1: Hook de formateo automático (Básico)
Objetivo: Configurar Prettier para formatear automáticamente archivos TypeScript al editarlos.
Tareas:
- Crea
.claude/settings.jsoncon un hook PostToolUse - El hook debe ejecutarse cuando se use Write o Edit
- Debe formatear solo archivos
.tsy.tsx - Timeout de 30 segundos
Archivo a crear: .claude/hooks/format.sh
Criterio de éxito:
- Editar un archivo .ts formatea automáticamente
- No intenta formatear archivos no-TypeScript
- Se completa en <5 segundos
Ver solución
.claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format.sh",
"timeout": 30
}
]
}
]
}
}.claude/hooks/format.sh:
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
if [[ "$file_path" =~ \.(ts|tsx)$ ]]; then
echo "Formateando: $file_path" >&2
npx prettier --write "$file_path" 2>&1
fi
exit 0chmod +x .claude/hooks/format.shEjercicio 2: Logging de comandos bash (Intermedio)
Objetivo: Registrar todos los comandos bash que Claude ejecuta para auditoría.
Tareas:
- Crear hook PreToolUse para la herramienta Bash
- Extraer el comando y descripción del JSON de entrada
- Guardar en
.claude/bash-log.txtcon timestamp - Formato:
[YYYY-MM-DD HH:MM:SS] descripción: comando
Criterio de éxito:
- Cada comando bash queda registrado
- El log es legible y tiene timestamps
- No afecta la ejecución de comandos
Ver solución
.claude/settings.json (añadir):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/log-bash.sh",
"timeout": 10
}
]
}
]
}
}.claude/hooks/log-bash.sh:
#!/usr/bin/env bash
set -euo pipefail
command=$(jq -r '.tool_input.command')
description=$(jq -r '.tool_input.description // "Sin descripción"')
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] $description: $command" >> "$CLAUDE_PROJECT_DIR/.claude/bash-log.txt"
exit 0chmod +x .claude/hooks/log-bash.shProbar:
# En sesión de Claude Code, pedir:
> Ejecuta ls -la
# Ver log:
cat .claude/bash-log.txtEjercicio 3: Tests automáticos relacionados (Intermedio)
Objetivo: Ejecutar automáticamente los tests relacionados con el archivo modificado.
Tareas:
- Crear hook PostToolUse que ejecute tests con Jest
- Solo ejecutar para archivos en
src/(no tests) - Usar
--findRelatedTestsde Jest - No fallar si no hay tests (usar
--passWithNoTests) - Timeout de 120 segundos
Criterio de éxito:
- Modificar
src/calculator.tsejecuta sus tests automáticamente - Modificar archivos fuera de
src/no ejecuta tests - Modificar
*.test.tsno ejecuta tests (evitar loops)
Ver solución
.claude/settings.json (añadir a PostToolUse existente):
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format.sh",
"timeout": 30
},
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/test-related.sh",
"timeout": 120
}
]
}
]
}
}.claude/hooks/test-related.sh:
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
# Solo procesar archivos TypeScript en src/
if [[ ! "$file_path" =~ ^.*/src/.* ]] || [[ ! "$file_path" =~ \.ts$ ]]; then
exit 0
fi
# No ejecutar tests para archivos de test
if [[ "$file_path" =~ \.test\.ts$ ]]; then
echo "Archivo de test modificado, skipping ejecución" >&2
exit 0
fi
echo "Ejecutando tests relacionados con: $file_path" >&2
npm test -- \
--findRelatedTests "$file_path" \
--passWithNoTests \
2>&1 || {
echo "⚠ Algunos tests fallaron" >&2
exit 0 # No bloquear por tests fallidos
}
echo "✓ Tests completados" >&2
exit 0chmod +x .claude/hooks/test-related.shEjercicio 4: Protección de archivos sensibles (Avanzado)
Objetivo: Bloquear modificaciones a archivos sensibles como .env o configuraciones.
Tareas:
- Crear hook PreToolUse para Read, Write y Edit
- Verificar si el archivo es sensible (.env, .git/, secrets/, etc.)
- Si es sensible, bloquear con exit code 2
- Mostrar mensaje claro al usuario
Criterio de éxito:
- Intentar editar
.enves bloqueado - Intentar leer
.git/configes bloqueado - Archivos normales funcionan sin problemas
- El mensaje de error es claro
Ver solución
.claude/settings.json (añadir a PreToolUse):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/log-bash.sh",
"timeout": 10
}
]
},
{
"matcher": "Read|Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-sensitive.sh",
"timeout": 10
}
]
}
]
}
}.claude/hooks/block-sensitive.sh:
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path // empty')
if [[ -z "$file_path" ]]; then
exit 0
fi
# Lista de patrones prohibidos
sensitive_patterns=(
'\.env'
'\.env\.'
'\.git/'
'\.ssh/'
'secrets/'
'credentials'
'\.key$'
'\.pem$'
)
# Verificar cada patrón
for pattern in "${sensitive_patterns[@]}"; do
if echo "$file_path" | grep -qE "$pattern"; then
echo "❌ BLOQUEADO: Acceso denegado a archivo sensible" >&2
echo " Archivo: $file_path" >&2
echo " Razón: Coincide con patrón protegido: $pattern" >&2
exit 2 # Bloquear operación
fi
done
exit 0chmod +x .claude/hooks/block-sensitive.shProbar:
# En sesión de Claude Code:
> Crea un archivo .env con contenido de prueba
# Debería ser bloqueado con mensaje de errorEjercicio 5: Sistema completo con notificaciones (Avanzado)
Objetivo: Configurar un sistema completo que incluya formateo, linting, tests y notificaciones.
Tareas:
- Combinar todos los hooks anteriores
- Añadir hook Stop para notificar cuando Claude termina
- Añadir hook SessionStart para inicializar log de sesión
- Optimizar timeouts según operación
- Implementar cache en formateo
Criterio de éxito:
- Al editar un archivo: formateo + tests automáticos
- Al ejecutar comando bash: queda registrado
- Cuando Claude termina: notificación desktop
- Archivos sensibles están protegidos
- Todo funciona rápido (<5s para formateo, <30s para tests)
Ver solución
.claude/settings.json (configuración completa):
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo \"[$(date -Iseconds)] Sesión iniciada\" >> \"$CLAUDE_PROJECT_DIR/.claude/session.log\"",
"timeout": 10
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/log-bash.sh",
"timeout": 10
}
]
},
{
"matcher": "Read|Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-sensitive.sh",
"timeout": 10
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format-cached.sh",
"timeout": 30
},
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/test-related.sh",
"timeout": 120
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/notify.sh",
"timeout": 5
}
]
}
]
}
}.claude/hooks/format-cached.sh:
#!/usr/bin/env bash
set -euo pipefail
file_path=$(jq -r '.tool_input.file_path')
# Solo TypeScript
if [[ ! "$file_path" =~ \.(ts|tsx)$ ]]; then
exit 0
fi
# Cache
cache_dir="$CLAUDE_PROJECT_DIR/.claude/cache"
mkdir -p "$cache_dir"
content_hash=$(md5sum "$file_path" | cut -d' ' -f1)
cache_key="$cache_dir/format.$(basename "$file_path").$content_hash"
if [[ -f "$cache_key" ]]; then
echo "Cache hit, skipping format" >&2
exit 0
fi
echo "Formateando: $file_path" >&2
npx prettier --write "$file_path" 2>&1
touch "$cache_key"
find "$cache_dir" -type f -mtime +7 -delete 2>/dev/null || true
exit 0.claude/hooks/notify.sh:
#!/usr/bin/env bash
set -euo pipefail
# Detectar sistema operativo
if command -v notify-send > /dev/null; then
notify-send "Claude Code" "Claude ha terminado de trabajar" --icon=dialog-information
elif command -v osascript > /dev/null; then
osascript -e 'display notification "Claude ha terminado de trabajar" with title "Claude Code"'
else
echo "✓ Claude ha terminado" >&2
fi
exit 0chmod +x .claude/hooks/*.shEjercicio 6: Monitoreo de performance (Desafío)
Objetivo: Implementar un wrapper que mida la duración de cada hook y alerte si son lentos.
Tareas:
- Crear
performance-wrapper.shque ejecute otros hooks midiendo tiempo - Registrar duración en
.claude/performance.log - Alertar si un hook tarda >5 segundos
- Modificar settings.json para usar el wrapper
Criterio de éxito:
- Cada hook queda registrado con su duración
- Puedes ver estadísticas de performance
- Recibes alertas si algo es lento
Ver solución
.claude/hooks/performance-wrapper.sh:
#!/usr/bin/env bash
set -euo pipefail
TARGET_HOOK="${1:-}"
THRESHOLD_SECONDS="${2:-5}"
if [[ -z "$TARGET_HOOK" ]]; then
echo "Uso: performance-wrapper.sh <hook-script> [threshold]" >&2
exit 1
fi
# Leer JSON de stdin
json_input=$(cat)
# Ejecutar hook con timing
start=$(date +%s.%N)
echo "$json_input" | "$TARGET_HOOK"
exit_code=$?
end=$(date +%s.%N)
# Calcular duración
duration=$(echo "$end - $start" | bc)
# Log de performance
perf_log="$CLAUDE_PROJECT_DIR/.claude/performance.log"
echo "$(date -Iseconds),$(basename "$TARGET_HOOK"),$duration,$exit_code" >> "$perf_log"
# Alertar si es lento
if (( $(echo "$duration > $THRESHOLD_SECONDS" | bc -l) )); then
echo "⚠ Hook lento: $(basename "$TARGET_HOOK") tomó ${duration}s" >&2
fi
exit $exit_codeModificar .claude/settings.json para usar wrapper:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/performance-wrapper.sh .claude/hooks/format-cached.sh 3",
"timeout": 40
},
{
"type": "command",
"command": ".claude/hooks/performance-wrapper.sh .claude/hooks/test-related.sh 30",
"timeout": 150
}
]
}
]
}
}Ver estadísticas:
# Ver hooks más lentos
cat .claude/performance.log | sort -t, -k3 -rn | head -5
# Ver promedio
awk -F, '{sum+=$3; count++} END {print "Promedio:", sum/count "s"}' .claude/performance.logVerificación
Checklist final
Verifica que tu configuración cumple con:
- Formateo automático funciona
- Tests se ejecutan solo para archivos relevantes
- Comandos bash quedan registrados
- Archivos sensibles están protegidos
- Recibes notificaciones cuando Claude termina
- Todos los hooks terminan en tiempo razonable (<2 minutos)
- Cache evita trabajo duplicado
- Logs son legibles y útiles
- No hay errores en modo verbose (
claude --verbose)
Comandos de verificación
# Ver estructura
tree .claude/
# Ver configuración
cat .claude/settings.json | jq
# Ver permisos de scripts
ls -la .claude/hooks/
# Ver logs
tail .claude/bash-log.txt
tail .claude/session.log
tail .claude/performance.log
# Probar hook manualmente
echo '{"tool_input":{"file_path":"src/calculator.ts"},"tool_name":"Write"}' | \
.claude/hooks/format.shDesafíos extra
Si quieres ir más allá:
- Multi-lenguaje: Adaptar hooks para soportar Python además de TypeScript
- Git integration: Auto-commit después de cambios (con mensaje descriptivo)
- Slack notifications: Notificar al equipo cuando completas tareas
- Security scanning: Integrar herramientas como TruffleHog o Bandit
- Code coverage: Monitorear cobertura de tests y alertar si baja
- Conditional execution: Ejecutar tests completos solo en branch main
Recursos
- 5.1 ¿Qué son los hooks?
- 5.2 Tipos de hooks
- 5.3 Configuración de hooks
- 5.4 Casos de uso avanzados
- 5.5 Timeout de hooks
¡Felicidades! Has completado la práctica del Módulo 5. Ahora tienes un sistema completo de automatización con hooks que mejorará significativamente tu flujo de trabajo con Claude Code.