Módulo 5: Skills y Hooks: automatizar tu workflow
Automatizar validaciones con Hooks
Automatizar validaciones con Hooks
Descripción
En la cápsula anterior aprendiste la mecánica de los hooks: los 5 eventos, matchers, return codes, variables de entorno. Ahora viene la aplicación práctica: construir un pipeline de calidad automatizado que valida el trabajo de Claude Code en tiempo real.
La idea es simple pero poderosa: cada vez que Claude escribe código, una serie de validaciones se ejecutan automáticamente. Linting, formateo, type checking, tests. Si algo falla, Claude lo ve y lo corrige antes de que tú tengas que intervenir. Es como tener un CI/CD que corre en cada keystroke, no en cada push.
Esta cápsula cubre 5 patterns de validación, cómo encadenarlos en un pipeline, comparaciones con CI/CD y git hooks, y los pitfalls que debes evitar para que tus hooks no se conviertan en un cuello de botella.
El poder de las validaciones automáticas
El problema sin hooks
Sin hooks, el flujo de calidad depende de tu memoria:
Claude escribe archivo
→ Tú: "Ejecuta eslint"
→ Claude ejecuta eslint → 3 errores
→ Tú: "Corrígelos"
→ Claude corrige
→ Tú: "Ahora ejecuta prettier"
→ Claude ejecuta prettier
→ Tú: "Ejecuta los tests"
→ Claude ejecuta tests → 1 falla
→ Tú: "Corrige el test"
→ ...
Son 6-8 interacciones manuales por cada archivo. Si Claude modifica 5 archivos en una tarea, son 30-40 interacciones solo de validación.
El flujo con hooks
Claude escribe archivo
→ [Hook PostToolUse] Prettier formatea automáticamente
→ [Hook PostToolUse] ESLint valida → errores visibles para Claude
→ Claude ve los errores y los corrige
→ [Hook PostToolUse] ESLint valida de nuevo → 0 errores
→ [Hook PostToolUse] Tests relacionados pasan
→ Siguiente archivo...
0 interacciones manuales de validación. Claude se auto-corrige basándose en el feedback de los hooks.
La clave: feedback loop automático
Lo más poderoso de los hooks no es que ejecutan validaciones — es que crean un feedback loop. Claude ve el output del hook, interpreta los errores, y los corrige en la siguiente acción. Es auto-corrección en tiempo real.
┌─────────┐ ┌──────────┐ ┌───────────┐
│ Claude │────▶│ Hook │────▶│ Output │
│ escribe │ │ valida │ │ (errores │
│ │ │ │ │ o ✅) │
└─────────┘ └──────────┘ └─────┬─────┘
▲ │
│ FEEDBACK LOOP │
└────────────────────────────────┘
Claude lee el output y corrige
Pattern 1: Lint en cada escritura
El pattern más común y más impactante. Cada vez que Claude escribe un archivo, el linter se ejecuta automáticamente.
Configuración básica
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"command": "./scripts/hooks/lint-on-write.sh"
}
]
}
}
Script del hook
#!/bin/bash
# scripts/hooks/lint-on-write.sh
FILE="$CLAUDE_FILE_PATH"
case "$FILE" in
*.ts|*.tsx)
npx eslint "$FILE" --quiet 2>&1
;;
*.js|*.jsx)
npx eslint "$FILE" --quiet 2>&1
;;
*.py)
ruff check "$FILE" 2>&1
;;
*.css|*.scss)
npx stylelint "$FILE" --quiet 2>&1
;;
esac
exit 0
Por qué exit 0 y no el exit code del linter
En PostToolUse, el archivo ya está escrito. Retornar un código de error no "deshace" la escritura — solo informa a Claude que algo está mal. Claude ve el output del linter y puede corregir en su siguiente acción. Por eso usamos exit 0: queremos que Claude vea los errores como información, no como un bloqueo.
Con auto-fix
Si prefieres que el linter corrija automáticamente lo que pueda:
#!/bin/bash
FILE="$CLAUDE_FILE_PATH"
case "$FILE" in
*.ts|*.tsx|*.js|*.jsx)
npx eslint "$FILE" --fix --quiet 2>&1
;;
*.py)
ruff check "$FILE" --fix 2>&1
;;
esac
exit 0
--fix corrige problemas automáticos (espacios, imports, etc.). Los problemas lógicos se reportan para que Claude los resuelva.
Pattern 2: Format automático
Prettier (o tu formatter) se ejecuta automáticamente después de cada escritura. Garantiza que todo el código generado por Claude sigue el estilo del proyecto.
Configuración
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"command": "npx prettier --write $CLAUDE_FILE_PATH --log-level=warn 2>/dev/null || true"
}
]
}
}
¿Format antes o después del lint?
El orden importa:
Opción A: Format → Lint
Prettier formatea → ESLint valida el código formateado
✅ Menos conflictos (Prettier y ESLint no se contradicen)
Opción B: Lint → Format
ESLint valida → Prettier formatea
❌ ESLint puede reportar errores de formato que Prettier va a arreglar de todos modos
Recomendación: Format primero, lint después:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"command": "npx prettier --write $CLAUDE_FILE_PATH --log-level=warn 2>/dev/null || true"
},
{
"matcher": "Write",
"command": "npx eslint $CLAUDE_FILE_PATH --quiet 2>/dev/null || true"
}
]
}
}
Pattern 3: Tests después de implementación
Ejecutar tests relacionados automáticamente después de que Claude modifica un archivo de código.
El desafío: ¿qué tests ejecutar?
Ejecutar toda la suite en cada escritura es demasiado lento. La clave es ejecutar solo los tests relacionados con el archivo modificado.
Estrategia: mapeo archivo → test
#!/bin/bash
# scripts/hooks/run-related-tests.sh
FILE="$CLAUDE_FILE_PATH"
# Solo archivos de source (no configs, no docs)
if [[ ! "$FILE" == src/* ]] && [[ ! "$FILE" == app/* ]]; then
exit 0
fi
# Si es un archivo de test, ejecutarlo directamente
if [[ "$FILE" == *test* ]] || [[ "$FILE" == *spec* ]]; then
echo "Ejecutando test: $FILE"
npx vitest run "$FILE" --reporter=dot 2>&1
exit 0
fi
# Buscar test correspondiente
BASENAME=$(basename "$FILE" | sed 's/\.[^.]*$//')
TEST_FILES=$(find . -name "*${BASENAME}*test*" -o -name "*${BASENAME}*spec*" -o -name "test_${BASENAME}*" 2>/dev/null | head -3)
if [ -n "$TEST_FILES" ]; then
echo "Tests relacionados encontrados:"
for TEST in $TEST_FILES; do
echo " → $TEST"
npx vitest run "$TEST" --reporter=dot 2>&1
done
else
echo "ℹ️ No se encontraron tests para $BASENAME"
fi
exit 0
Para proyectos Python
#!/bin/bash
FILE="$CLAUDE_FILE_PATH"
if [[ "$FILE" == *.py ]]; then
BASENAME=$(basename "$FILE" .py)
TEST_FILE="tests/test_${BASENAME}.py"
if [ -f "$TEST_FILE" ]; then
echo "Ejecutando: pytest $TEST_FILE -v"
pytest "$TEST_FILE" -v --tb=short 2>&1
fi
fi
exit 0
Cuándo ejecutar la suite completa
La suite completa es más apropiada para Stop que para PostToolUse:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"command": "./scripts/hooks/run-related-tests.sh"
}
],
"Stop": [
{
"command": "npm test --silent 2>&1 | tail -5"
}
]
}
}
Tests relacionados en cada escritura. Suite completa al final de la tarea.
Pattern 4: Validación de seguridad
Escanear código en busca de secrets, credenciales, y vulnerabilidades antes de permitir que se escriba.
PreToolUse para bloquear escritura insegura
#!/bin/bash
# scripts/hooks/security-check.sh
FILE="$CLAUDE_FILE_PATH"
INPUT="$CLAUDE_TOOL_INPUT"
# Detectar posibles secrets en el contenido que se va a escribir
PATTERNS=(
"password\s*=\s*['\"]"
"api_key\s*=\s*['\"]"
"secret\s*=\s*['\"]"
"token\s*=\s*['\"][a-zA-Z0-9]"
"AWS_ACCESS_KEY"
"PRIVATE_KEY"
"-----BEGIN RSA"
"-----BEGIN OPENSSH"
)
for PATTERN in "${PATTERNS[@]}"; do
if echo "$INPUT" | grep -iEq "$PATTERN"; then
echo "⛔ SEGURIDAD: Se detectó un posible secret/credencial"
echo " Patrón: $PATTERN"
echo " Archivo: $FILE"
echo ""
echo " Usa variables de entorno en lugar de hardcodear secrets."
echo " Ejemplo: os.environ['API_KEY'] o process.env.API_KEY"
exit 1
fi
done
exit 0
Configuración
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"command": "./scripts/hooks/security-check.sh"
}
]
}
}
Si Claude intenta escribir un archivo con password = "admin123", el hook lo bloquea y le sugiere usar variables de entorno. Claude ve el mensaje y reescribe usando os.environ['PASSWORD'].
PostToolUse para auditoría
Si prefieres no bloquear sino alertar:
#!/bin/bash
# scripts/hooks/security-audit.sh
FILE="$CLAUDE_FILE_PATH"
if [ -f "$FILE" ]; then
ISSUES=$(grep -inE "(password|api_key|secret|token)\s*=\s*['\"]" "$FILE" 2>/dev/null)
if [ -n "$ISSUES" ]; then
echo "⚠️ ALERTA DE SEGURIDAD en $FILE:"
echo "$ISSUES"
echo ""
echo "Considera usar variables de entorno para estos valores."
fi
fi
exit 0
Pattern 5: Type checking
Para proyectos TypeScript, ejecutar el compilador después de cada escritura detecta errores de tipos en tiempo real.
Configuración
#!/bin/bash
# scripts/hooks/type-check.sh
FILE="$CLAUDE_FILE_PATH"
if [[ "$FILE" == *.ts ]] || [[ "$FILE" == *.tsx ]]; then
# Type check solo del archivo modificado (más rápido que todo el proyecto)
ERRORS=$(npx tsc --noEmit 2>&1 | grep "$FILE")
if [ -n "$ERRORS" ]; then
echo "⚠️ Errores de tipo en $FILE:"
echo "$ERRORS"
fi
fi
exit 0
Consideración de performance
tsc --noEmit puede tardar varios segundos en proyectos grandes. Opciones:
- Ejecutar solo en Stop (no en cada escritura):
{
"hooks": {
"Stop": [
{
"command": "npx tsc --noEmit 2>&1 | head -20"
}
]
}
}
- Usar
tsc --noEmitcon--incrementalpara cachear resultados:
npx tsc --noEmit --incremental 2>&1 | grep "$FILE"
- Aceptar el trade-off si el proyecto es pequeño (< 100 archivos):
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"command": "npx tsc --noEmit 2>&1 | grep $CLAUDE_FILE_PATH || true"
}
]
}
}
Construir un pipeline completo
El pipeline recomendado
Para un proyecto TypeScript/React típico, este es el pipeline óptimo:
PostToolUse (Write):
1. Prettier (format) → ~100ms por archivo
2. ESLint (lint) → ~200ms por archivo
3. Tests relacionados → ~1-3s por archivo
Stop:
4. tsc --noEmit (types) → ~3-5s total
5. Suite completa de tests → variable
Configuración del pipeline
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"command": "./scripts/hooks/security-check.sh"
},
{
"matcher": "Execute",
"command": "./scripts/hooks/safety-gate.sh"
}
],
"PostToolUse": [
{
"matcher": "Write",
"command": "./scripts/hooks/format.sh"
},
{
"matcher": "Write",
"command": "./scripts/hooks/lint.sh"
},
{
"matcher": "Write",
"command": "./scripts/hooks/related-tests.sh"
}
],
"Stop": [
{
"command": "./scripts/hooks/type-check.sh"
},
{
"command": "./scripts/hooks/full-test-suite.sh"
}
]
}
}
Performance del pipeline
| Hook | Tiempo típico | Frecuencia |
|---|---|---|
| Prettier | ~100ms | Por archivo escrito |
| ESLint | ~200ms | Por archivo escrito |
| Tests relacionados | ~1-3s | Por archivo escrito |
| tsc --noEmit | ~3-5s | Por tarea completada |
| Suite completa | ~10-30s | Por tarea completada |
Para una tarea donde Claude escribe 5 archivos, el overhead total es:
- PostToolUse: 5 × (~300ms + ~2s) ≈ ~11.5s distribuidos durante la tarea
- Stop: ~5s + ~20s = ~25s al final de la tarea
Esto es aceptable. Si supera estos tiempos, optimiza los hooks más lentos.
Optimización: Conditional hooks con if (marzo 2026)
Feature reciente: los hooks pueden declarar una condición con if usando permission rule syntax. El hook solo se ejecuta cuando el tool call matchea la condición, reduciendo overhead en sesiones con muchas operaciones:
{
"hooks": {
"PreToolUse": [{
"hooks": [{
"if": "Bash(git commit *)",
"type": "command",
"command": ".claude/hooks/lint-staged.sh"
}]
}]
}
}
Con if: "Bash(git commit *)", el lint-staged solo corre en git commits — no en cada bash call. Útil cuando tienes hooks específicos a ciertas operaciones.
Comparaciones y decisiones
Hooks vs CI/CD
| Aspecto | Hooks de Claude Code | CI/CD (GitHub Actions, etc.) |
|---|---|---|
| Cuándo se ejecuta | Durante el desarrollo, en tiempo real | Después del push, en remoto |
| Feedback | Inmediato (Claude corrige al instante) | Diferido (minutos después del push) |
| Qué valida | Cada archivo individual | Todo el proyecto |
| Costo de error | Bajo (se corrige antes de commit) | Alto (ya está en el repo) |
| Performance | Debe ser rápido (< 5s por hook) | Puede tardar minutos |
No compiten — son capas complementarias:
Capa 1: Hooks de Claude Code → Catch errores durante desarrollo
Capa 2: Git pre-commit hooks → Catch errores al commitear
Capa 3: CI/CD → Catch errores al pushear/mergear
Cuanto antes detectas un error, más barato es corregirlo. Los hooks de Claude Code son la primera línea de defensa.
Hooks de Claude Code vs pre-commit (git)
| Aspecto | Claude Code Hooks | Git pre-commit |
|---|---|---|
| Granularidad | Por archivo, por herramienta | Por commit |
| Agent-aware | ✅ Sabe qué herramienta usó Claude | ❌ No sabe quién hizo el cambio |
| Feedback a Claude | ✅ Claude ve y corrige | ❌ No interactúa con Claude |
| Timing | En tiempo real (cada escritura) | Al commitear |
Cuándo usar solo hooks ligeros
Si tu proyecto es grande y los hooks son lentos, reduce al mínimo:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"command": "npx prettier --write $CLAUDE_FILE_PATH 2>/dev/null || true"
}
],
"Stop": [
{
"command": "npm test --silent 2>&1 | tail -5"
}
]
}
}
Solo format automático + tests al final. Sin lint ni type check en hooks (dejarlo para CI/CD).
Patterns avanzados
Pattern: Hook condicional por branch
#!/bin/bash
# Solo ejecutar validaciones estrictas en main/develop
BRANCH=$(git branch --show-current 2>/dev/null)
case "$BRANCH" in
main|develop|staging)
# Validación estricta
npx eslint "$CLAUDE_FILE_PATH" --max-warnings=0 2>&1
;;
*)
# Validación relajada (solo errores, no warnings)
npx eslint "$CLAUDE_FILE_PATH" --quiet 2>&1 || true
;;
esac
exit 0
Pattern: Hook con cache de resultados
#!/bin/bash
# Cachear resultados de lint para evitar re-ejecutar en archivos sin cambios
FILE="$CLAUDE_FILE_PATH"
CACHE_DIR=".claude/cache/lint"
mkdir -p "$CACHE_DIR"
FILE_HASH=$(md5sum "$FILE" 2>/dev/null | cut -d' ' -f1)
CACHE_FILE="$CACHE_DIR/$(echo $FILE | tr '/' '_').hash"
if [ -f "$CACHE_FILE" ] && [ "$(cat $CACHE_FILE)" = "$FILE_HASH" ]; then
exit 0
fi
npx eslint "$FILE" --quiet 2>&1
RESULT=$?
if [ $RESULT -eq 0 ]; then
echo "$FILE_HASH" > "$CACHE_FILE"
fi
exit 0
Pattern: Notificación al terminar tarea larga
#!/bin/bash
# scripts/hooks/notify-done.sh
MODIFIED=$(git diff --name-only 2>/dev/null | wc -l | tr -d ' ')
if [ "$MODIFIED" -gt 5 ]; then
echo "📋 Tarea grande completada: $MODIFIED archivos modificados"
echo "Archivos:"
git diff --name-only 2>/dev/null | head -10
if [ "$MODIFIED" -gt 10 ]; then
echo " ... y $(($MODIFIED - 10)) más"
fi
fi
Pitfalls y edge cases
Pitfall 1: Hooks demasiado lentos
Síntoma: Claude tarda mucho entre acciones. La sesión se siente lenta.
Diagnóstico: Mide el tiempo de tus hooks:
time ./scripts/hooks/post-write-validate.sh
Solución: Cualquier hook PostToolUse debe completarse en < 3 segundos. Si tarda más:
- Mueve la validación a Stop
- Usa
--incrementalo caching - Reduce el scope (solo el archivo modificado, no todo el proyecto)
Pitfall 2: Hooks demasiado estrictos
Síntoma: Claude no puede completar tareas porque los PreToolUse bloquean operaciones legítimas.
Ejemplo problemático:
# Bloquea CUALQUIER archivo que contenga "TODO"
if grep -q "TODO" "$FILE"; then
echo "No se permiten TODOs en el código"
exit 1
fi
Solución: Los PreToolUse que bloquean deben ser para verdaderos riesgos de seguridad, no para preferencias de estilo. Usa PostToolUse para warnings:
# PostToolUse: informa pero no bloquea
TODO_COUNT=$(grep -c "TODO" "$FILE" 2>/dev/null || echo "0")
if [ "$TODO_COUNT" -gt 0 ]; then
echo "ℹ️ $TODO_COUNT TODOs encontrados en $FILE"
fi
exit 0
Pitfall 3: No testear los hooks
El error: Configurar hooks y asumir que funcionan.
La solución: Testea cada hook manualmente antes de ponerlo en producción:
# Simular variables de entorno
CLAUDE_FILE_PATH="src/components/Test.tsx" \
CLAUDE_TOOL_NAME="Write" \
./scripts/hooks/post-write-validate.sh
Pitfall 4: Hooks que escriben archivos (loop potencial)
El error: Un PostToolUse hook que modifica archivos (ej. Prettier con --write).
El riesgo: Si Claude Code trata la modificación de Prettier como una nueva escritura, se dispara el hook de nuevo.
La realidad: Claude Code tiene protecciones contra esto — los cambios hechos por hooks no disparan nuevos hooks. Pero es buena práctica diseñar hooks idempotentes (ejecutar dos veces produce el mismo resultado que una).
Pitfall 5: Ignorar el output de los hooks
El error: Configurar hooks pero no revisar si Claude está actuando sobre su feedback.
La solución: Verifica que Claude corrige los errores que los hooks reportan. Si Claude ignora consistentemente el output de un hook, puede ser que:
- El output es demasiado verbose (Claude pierde los errores en el ruido)
- El formato no es claro (Claude no entiende qué corregir)
- El hook reporta demasiados false positives (Claude aprende a ignorarlo)
Haz el output conciso y accionable.
Ejemplo completo integrado
Escenario: configuras un pipeline de validación completo para un proyecto FastAPI + React (monorepo).
Estructura
scripts/hooks/
├── security-check.sh
├── safety-gate.sh
├── format.sh
├── lint.sh
├── related-tests.sh
├── type-check.sh
└── task-summary.sh
settings.json
{
"hooks": {
"PreToolUse": [
{ "matcher": "Write", "command": "./scripts/hooks/security-check.sh" },
{ "matcher": "Execute", "command": "./scripts/hooks/safety-gate.sh" }
],
"PostToolUse": [
{ "matcher": "Write", "command": "./scripts/hooks/format.sh" },
{ "matcher": "Write", "command": "./scripts/hooks/lint.sh" },
{ "matcher": "Write", "command": "./scripts/hooks/related-tests.sh" }
],
"Stop": [
{ "command": "./scripts/hooks/type-check.sh" },
{ "command": "./scripts/hooks/task-summary.sh" }
]
}
}
Script multi-lenguaje: format.sh
#!/bin/bash
FILE="$CLAUDE_FILE_PATH"
case "$FILE" in
*.ts|*.tsx|*.js|*.jsx|*.css|*.scss|*.json|*.md)
npx prettier --write "$FILE" --log-level=warn 2>/dev/null || true
;;
*.py)
ruff format "$FILE" 2>/dev/null || true
;;
esac
exit 0
Script multi-lenguaje: lint.sh
#!/bin/bash
FILE="$CLAUDE_FILE_PATH"
case "$FILE" in
*.ts|*.tsx|*.js|*.jsx)
ERRORS=$(npx eslint "$FILE" --quiet 2>&1)
if [ -n "$ERRORS" ]; then
echo "$ERRORS"
fi
;;
*.py)
ERRORS=$(ruff check "$FILE" 2>&1)
if [ -n "$ERRORS" ]; then
echo "$ERRORS"
fi
;;
esac
exit 0
task-summary.sh
#!/bin/bash
echo ""
echo "=== Resumen de tarea ==="
MODIFIED=$(git diff --name-only 2>/dev/null | wc -l | tr -d ' ')
NEW=$(git ls-files --others --exclude-standard 2>/dev/null | wc -l | tr -d ' ')
echo "Archivos modificados: $MODIFIED"
echo "Archivos nuevos: $NEW"
# Type check (TypeScript)
if [ -f "tsconfig.json" ]; then
TSC_ERRORS=$(npx tsc --noEmit 2>&1 | grep -c "error TS" || echo "0")
if [ "$TSC_ERRORS" -gt 0 ]; then
echo "TypeScript: ❌ $TSC_ERRORS errores"
else
echo "TypeScript: ✅ Sin errores"
fi
fi
# Tests
TEST_OUTPUT=$(npm test --silent 2>&1)
if [ $? -eq 0 ]; then
echo "Tests: ✅ Passing"
else
echo "Tests: ❌ Failing"
echo "$TEST_OUTPUT" | tail -5
fi
echo "========================"
Ejercicios prácticos
Ejercicio 1: Básico — Lint automático
Configura un hook que ejecute el linter de tu proyecto automáticamente después de cada escritura.
Requisitos:
- Detecta el tipo de archivo (JS/TS/Python/CSS)
- Ejecuta el linter correcto para cada tipo
- No bloquea (informativo)
- Output limpio (solo errores, no warnings)
Solución
Usa el script lint.sh del ejemplo integrado. Adapta los comandos de lint a los que usa tu proyecto (ESLint, Ruff, Stylelint, etc.).
Configura en .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"command": "./scripts/hooks/lint.sh"
}
]
}
}
Verifica: pide a Claude que cree un archivo con un error de lint intencional y confirma que el hook lo reporta.
Ejercicio 2: Básico — Format + Lint pipeline
Configura un pipeline de 2 pasos: primero format, luego lint.
Requisitos:
- Prettier (o tu formatter) primero
- Linter después
- Ambos solo en archivos de código (no en configs, no en markdowns)
Solución
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"command": "./scripts/hooks/format.sh"
},
{
"matcher": "Write",
"command": "./scripts/hooks/lint.sh"
}
]
}
}
El orden es importante: format antes de lint para evitar que el linter reporte problemas de formato que Prettier va a corregir.
Ejercicio 3: Intermedio — Security check
Crea un hook PreToolUse que bloquee la escritura de archivos que contengan secrets hardcodeados.
Requisitos:
- Detectar al menos 5 patterns (password, api_key, secret, token, private_key)
- Bloquear la escritura (exit 1)
- Mensaje de error claro con sugerencia de cómo hacerlo correctamente
- No bloquear archivos .env.example (que es legítimo que tengan placeholders)
Solución
Adapta el script security-check.sh del Pattern 4. Agrega la excepción:
#!/bin/bash
FILE="$CLAUDE_FILE_PATH"
INPUT="$CLAUDE_TOOL_INPUT"
# No validar archivos de ejemplo
if [[ "$FILE" == *.example ]] || [[ "$FILE" == *.sample ]]; then
exit 0
fi
PATTERNS=(
"password\s*=\s*['\"][^{]"
"api_key\s*=\s*['\"][^{]"
"secret\s*=\s*['\"][^{]"
"token\s*=\s*['\"][a-zA-Z0-9]"
"AWS_ACCESS_KEY_ID\s*=\s*['\"]AK"
"PRIVATE.KEY\s*=\s*['\"]"
)
for PATTERN in "${PATTERNS[@]}"; do
if echo "$INPUT" | grep -iEq "$PATTERN"; then
echo "⛔ Secret detectado. Usa variables de entorno."
echo " Ejemplo: process.env.API_KEY o os.environ['API_KEY']"
exit 1
fi
done
exit 0
Ejercicio 4: Intermedio — Tests relacionados
Crea un hook que ejecute automáticamente los tests relacionados con el archivo que Claude acaba de escribir.
Requisitos:
- Mapear archivo de source → archivo de test
- Ejecutar solo los tests del archivo modificado (no toda la suite)
- Si no hay test correspondiente, mostrar un mensaje informativo
- Si el archivo ES un test, ejecutarlo directamente
Solución
Usa el script run-related-tests.sh del Pattern 3. Adapta las convenciones de naming a tu proyecto:
src/foo.ts→tests/foo.test.tsosrc/__tests__/foo.test.tsapp/services/foo.py→tests/test_foo.py
Testea manualmente con:
CLAUDE_FILE_PATH="src/components/Button.tsx" ./scripts/hooks/related-tests.sh
Ejercicio 5: Avanzado — Pipeline completo con los 5 eventos
Configura los 5 eventos reales de hooks con un pipeline de validación profesional.
Requisitos:
- PreToolUse: security check (Write) + safety gate (Execute)
- PostToolUse: format + lint + tests relacionados
- Stop: type check + resumen con estado
- Notification: alerta al completar tareas largas
Solución
Usa la configuración completa del ejemplo integrado de esta cápsula. Crea cada script, hazlo ejecutable, y verifica:
- Pide a Claude que cree un archivo → ¿format + lint se ejecutan?
- Pide a Claude que escriba un secret → ¿el security check lo bloquea?
- Cuando Claude termina una tarea → ¿ves el resumen del hook Stop?
- ¿Se ejecuta el hook de Notification cuando Claude envía notificaciones?
Resumen
Lo que aprendiste en esta cápsula:
- Hooks crean un feedback loop: Claude ve errores → Claude corrige → hooks validan de nuevo
- 5 patterns de validación: lint, format, tests, seguridad, type checking
- El orden importa: format → lint → tests (en PostToolUse), suite completa (en Stop)
- Performance: PostToolUse hooks deben ser rápidos (< 3s). Validaciones pesadas van en Stop
- Hooks vs CI/CD: capas complementarias. Hooks = primera línea de defensa (en desarrollo). CI/CD = segunda línea (en push)
- Pitfalls clave: hooks lentos, hooks demasiado estrictos, no testear los hooks, ignorar el output
Siguiente cápsula: 05 - Permission system — cómo controlar qué puede y qué no puede hacer Claude Code en tu proyecto.
Recursos adicionales
Documentación oficial
- Hooks — Referencia completa de hooks y lifecycle events
- Settings — Configuración de hooks en diferentes scopes
- Best Practices — Recomendaciones para automatización
Herramientas de validación
- ESLint — Linter para JavaScript/TypeScript
- Prettier — Formatter de código
- Ruff — Linter y formatter para Python (extremadamente rápido)
Complementarios
- Interactive Mode — Cómo permisos interactúan con hooks
- GitHub Actions — CI/CD como complemento a hooks