Módulo 6: Hooks Avanzados y SDK Headless
2. SessionStart y PreToolUse Avanzado — Setup Automático y Validación
2. SessionStart y PreToolUse Avanzado — Setup Automático y Validación
Descripción
Cada vez que abres Claude Code, el primer minuto es siempre igual: verificas que las dependencias están instaladas, que el servidor de desarrollo no está corriendo en un puerto ocupado, que la base de datos tiene las migraciones al día. Es tedioso y repetitivo. SessionStart elimina ese ritual — un hook que se dispara automáticamente al arrancar cada sesión y ejecuta tu script de setup.
PreToolUse, por otro lado, es el guardián. Ya lo conoces de guías anteriores en su forma básica — un hook que se dispara antes de ejecutar una herramienta. Pero PreToolUse avanzado va más allá: validación condicional basada en el contenido del comando, bloqueo de operaciones peligrosas por exit code 2, restricción de rutas de archivos, y matchers con regex para capturar grupos de herramientas.
Al terminar esta cápsula sabrás configurar hooks en settings.json y en frontmatter de subagents, entenderás el formato JSON que los hooks reciben via stdin, y dominarás los tres exit codes que controlan el flujo de ejecución. Tu Claude Code arrancará configurado automáticamente y bloqueará operaciones peligrosas antes de que se ejecuten.
SessionStart: Configuración Automática al Iniciar
El problema
Sin SessionStart, tu inicio de sesión se ve así:
Tú: "Verifica que las deps estén instaladas"
Claude: [corre npm install, pip install, etc.]
Tú: "Verifica que la DB esté corriendo"
Claude: [chequea PostgreSQL, Redis]
Tú: "Corre las migraciones pendientes"
Claude: [ejecuta alembic upgrade head]
Tú: "Ahora sí, implementa la feature X"
Cuatro interacciones desperdiciadas. Con SessionStart:
[Sesión arranca → SessionStart hook ejecuta setup automático]
Tú: "Implementa la feature X"
Configuración en settings.json
Los hooks se configuran en el archivo de settings de Claude Code. Hay tres niveles:
| Nivel | Archivo | Scope |
|---|---|---|
| Proyecto | .claude/settings.json | Solo este proyecto |
| Usuario | ~/.claude/settings.json | Todos tus proyectos |
| Empresa | Configuración administrada | Toda la organización |
Para hooks específicos del proyecto, usa .claude/settings.json:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "./scripts/session-setup.sh"
}
]
}
]
}
}
El script de setup
./scripts/session-setup.sh:
#!/bin/bash
echo "🔧 Configurando entorno de desarrollo..."
# Verificar Node.js
if ! command -v node &> /dev/null; then
echo "ERROR: Node.js no encontrado"
exit 1
fi
# Instalar dependencias si package-lock.json cambió
if [ package-lock.json -nt node_modules/.package-lock.json ] 2>/dev/null; then
echo "📦 Instalando dependencias..."
npm ci --silent
fi
# Verificar Python virtual env
if [ -f "requirements.txt" ]; then
if [ ! -d ".venv" ]; then
echo "🐍 Creando virtual environment..."
python3 -m venv .venv
fi
source .venv/bin/activate
pip install -r requirements.txt -q
fi
# Verificar base de datos
if command -v pg_isready &> /dev/null; then
if ! pg_isready -q 2>/dev/null; then
echo "⚠️ PostgreSQL no está corriendo"
exit 1
fi
fi
# Migraciones pendientes
if [ -d "alembic" ]; then
CURRENT=$(alembic current 2>/dev/null | tail -1)
HEAD=$(alembic heads 2>/dev/null | tail -1)
if [ "$CURRENT" != "$HEAD" ]; then
echo "📊 Ejecutando migraciones pendientes..."
alembic upgrade head
fi
fi
echo "✅ Entorno listo"
exit 0
Recuerda hacer el script ejecutable:
chmod +x ./scripts/session-setup.sh
SessionStart no tiene matcher
A diferencia de PreToolUse o PostToolUse, SessionStart no tiene campo matcher. Se dispara una vez al iniciar cualquier sesión, sin condiciones. Si necesitas lógica condicional, ponla dentro del script:
#!/bin/bash
# Solo ejecutar setup completo en días laborales
DAY=$(date +%u)
if [ "$DAY" -gt 5 ]; then
echo "Fin de semana — skip setup completo"
exit 0
fi
# Setup completo aquí...
Exit codes en SessionStart
| Exit Code | Efecto |
|---|---|
0 | Sesión arranca normalmente |
1 | Error reportado a Claude — la sesión continúa pero Claude sabe que algo falló |
2 | Sesión bloqueada — Claude Code no inicia |
Exit code 2 en SessionStart es drástico: impide que la sesión arranque. Úsalo solo para condiciones críticas que harían la sesión inútil (ej: la base de datos de producción no es accesible para un proyecto que la necesita).
PreToolUse Avanzado: Validación y Bloqueo
Más allá del básico
En guías anteriores, usaste PreToolUse para validaciones simples. Ahora vas a crear hooks que:
- Leen el JSON de input para inspeccionar qué va a hacer la herramienta
- Toman decisiones condicionales basadas en el contenido
- Bloquean operaciones peligrosas con exit code 2
- Usan matchers con regex para capturar grupos de herramientas
El input JSON
Todo hook recibe información via stdin en formato JSON. Para PreToolUse, el JSON incluye:
{
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf /tmp/test",
"description": "Clean temp files"
},
"session_id": "abc123",
"transcript_path": "/tmp/claude/transcript-abc123.json"
}
Tu script puede leer este JSON y tomar decisiones:
#!/bin/bash
INPUT=$(cat -)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# Lógica de validación basada en el contenido
Pattern 1: Bloquear comandos peligrosos
./scripts/validate-bash.sh:
#!/bin/bash
INPUT=$(cat -)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if [ -z "$COMMAND" ]; then
exit 0
fi
BLOCKED_PATTERNS=(
"rm -rf /"
"rm -rf ~"
"rm -rf \."
"DROP DATABASE"
"DROP TABLE"
"truncate"
"mkfs"
"dd if="
":(){:|:&};:"
)
for pattern in "${BLOCKED_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -qi "$pattern"; then
echo "BLOCKED: Comando peligroso detectado: $pattern"
echo "Comando intentado: $COMMAND"
exit 2
fi
done
DANGEROUS_PATTERNS=(
"sudo"
"chmod 777"
"curl.*|.*sh"
"wget.*|.*bash"
)
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -qi "$pattern"; then
echo "WARNING: Comando potencialmente peligroso: $pattern"
echo "Comando: $COMMAND"
exit 1
fi
done
exit 0
Configuración en settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "./scripts/validate-bash.sh"
}
]
}
]
}
}
Pattern 2: Restricción de rutas de archivos
Impedir que Claude modifique archivos fuera de ciertos directorios:
./scripts/validate-file-paths.sh:
#!/bin/bash
INPUT=$(cat -)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
PROTECTED_PATHS=(
".env"
".env.local"
".env.production"
"credentials"
"secrets"
".ssh"
".aws"
)
for protected in "${PROTECTED_PATHS[@]}"; do
if echo "$FILE_PATH" | grep -qi "$protected"; then
echo "BLOCKED: Acceso a archivo protegido: $FILE_PATH"
exit 2
fi
done
ALLOWED_DIRS=(
"src/"
"tests/"
"docs/"
"scripts/"
".claude/"
)
ALLOWED=false
for dir in "${ALLOWED_DIRS[@]}"; do
if echo "$FILE_PATH" | grep -q "^$dir"; then
ALLOWED=true
break
fi
done
if [ "$ALLOWED" = false ]; then
echo "WARNING: Archivo fuera de directorios permitidos: $FILE_PATH"
echo "Directorios permitidos: ${ALLOWED_DIRS[*]}"
exit 1
fi
exit 0
Configuración con matcher para múltiples herramientas:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "./scripts/validate-file-paths.sh"
}
]
}
]
}
}
Pattern 3: Validación condicional por contexto
Un hook que valida de forma diferente según el entorno:
./scripts/context-validator.sh:
#!/bin/bash
INPUT=$(cat -)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
BRANCH=$(git branch --show-current 2>/dev/null)
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
if [ "$TOOL_NAME" = "Bash" ]; then
if echo "$COMMAND" | grep -qiE "(git push|npm publish|deploy)"; then
echo "BLOCKED: Operación de deployment en branch principal"
echo "Crea un branch de feature primero"
exit 2
fi
fi
if [ "$TOOL_NAME" = "Write" ] || [ "$TOOL_NAME" = "Edit" ]; then
echo "WARNING: Editando en branch principal ($BRANCH)"
echo "Considera crear un branch de feature"
exit 1
fi
fi
exit 0
Matchers: Regex y pipe-separated
El campo matcher acepta dos formatos:
Nombre exacto:
{ "matcher": "Bash" }
Pipe-separated (OR):
{ "matcher": "Edit|Write" }
Regex:
{ "matcher": ".*" }
El matcher .* captura todas las herramientas — útil para logging universal.
Sin matcher: captura todo
Si omites el campo matcher, el hook se dispara para todas las herramientas:
{
"hooks": {
"PreToolUse": [
{
"hooks": [
{
"type": "command",
"command": "./scripts/log-all-tools.sh"
}
]
}
]
}
}
Hooks en Frontmatter de Subagents
Cuándo usar hooks en el frontmatter
Los hooks en settings.json aplican a toda la sesión. Los hooks en el frontmatter de un subagent aplican solo a ese subagent. Esto permite reglas específicas por agente:
---
name: safe-implementer
description: Implements code with extra safety checks
tools: Read, Write, Edit, Grep, Glob
hooks:
PreToolUse:
- matcher: "Write|Edit"
hooks:
- type: command
command: "./scripts/validate-file-paths.sh"
PostToolUse:
- matcher: "Write|Edit"
hooks:
- type: command
command: "./scripts/auto-lint.sh"
---
You are a careful implementer. Write clean, tested code.
Precedencia: frontmatter vs settings.json
Cuando un subagent tiene hooks en su frontmatter Y hay hooks en settings.json, ambos se ejecutan. El orden es:
- Settings.json hooks se ejecutan primero
- Frontmatter hooks se ejecutan después
Si cualquiera de los dos retorna exit code 2, la operación se bloquea.
Ejemplo: Subagent con restricciones estrictas
Un subagent que solo puede editar archivos en su directorio asignado:
---
name: auth-specialist
description: Only modifies files in src/auth/
tools: Read, Write, Edit, Grep, Glob
hooks:
PreToolUse:
- matcher: "Write|Edit"
hooks:
- type: command
command: |
#!/bin/bash
INPUT=$(cat -)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty')
if [ -n "$FILE" ] && ! echo "$FILE" | grep -q "^src/auth/"; then
echo "BLOCKED: auth-specialist solo puede editar src/auth/"
echo "Intentó editar: $FILE"
exit 2
fi
exit 0
---
You are an auth specialist. You ONLY modify files in src/auth/.
Con este hook, el sistema enforce técnicamente la restricción. Aunque el system prompt diga "solo edita src/auth/", los LLMs pueden cometer errores. El hook garantiza que ningún archivo fuera de src/auth/ sea modificado.
Múltiples Hooks por Evento
Encadenamiento
Puedes tener múltiples hooks para el mismo evento. Se ejecutan en orden:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "./scripts/validate-bash.sh"
}
]
},
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "./scripts/validate-file-paths.sh"
}
]
},
{
"hooks": [
{
"type": "command",
"command": "./scripts/log-all-tools.sh"
}
]
}
]
}
}
Cuando Claude ejecuta Edit:
validate-bash.sh→ NO se ejecuta (matcher no coincide)validate-file-paths.sh→ SÍ se ejecuta (matcher coincide)log-all-tools.sh→ SÍ se ejecuta (sin matcher = captura todo)
Si validate-file-paths.sh retorna exit 2, la operación se bloquea y log-all-tools.sh no se ejecuta.
Múltiples comandos dentro de un hook
Un hook entry puede tener múltiples comandos:
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "./scripts/validate-bash.sh"
},
{
"type": "command",
"command": "./scripts/log-bash.sh"
}
]
}
Ambos se ejecutan en orden. Si el primero retorna exit 2, el segundo no se ejecuta.
Configuración Completa: settings.json con SessionStart + PreToolUse
Un ejemplo completo que combina ambos:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "./scripts/session-setup.sh"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "./scripts/validate-bash.sh"
}
]
},
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "./scripts/validate-file-paths.sh"
}
]
}
]
}
}
Con esta configuración:
- Cada sesión arranca con setup automático
- Cada comando Bash se valida contra patrones peligrosos
- Cada escritura de archivo se valida contra rutas protegidas
Troubleshooting
"El hook SessionStart no se ejecuta"
Causa: El script no tiene permisos de ejecución, o la ruta es incorrecta.
Solución:
chmod +x ./scripts/session-setup.sh
# Verifica que la ruta sea relativa al root del proyecto
ls -la ./scripts/session-setup.sh
"El hook PreToolUse no bloquea nada"
Causa: El matcher no coincide con el nombre de la herramienta, o el script siempre retorna exit 0.
Solución:
# Testa el script manualmente
echo '{"tool_name": "Bash", "tool_input": {"command": "rm -rf /"}}' | ./scripts/validate-bash.sh
echo $?
"jq: command not found"
Causa: jq no está instalado. Los scripts de hooks que parsean JSON necesitan jq.
Solución:
# macOS
brew install jq
# Ubuntu/Debian
sudo apt-get install jq
# Si no puedes instalar jq, usa Python como alternativa:
COMMAND=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))")
"El hook bloquea todo — exit code 2 inesperado"
Causa: El script tiene un error que causa exit code != 0 por defecto.
Solución: Agrega exit 0 al final del script como default y asegúrate de que cada path de ejecución retorne un exit code explícito. Testa el script aislado antes de configurarlo como hook.
"El hook tarda demasiado y hace la sesión lenta"
Causa: El script de hook hace operaciones pesadas (npm install, compilación, requests de red).
Solución: Mantén los hooks ligeros (< 2 segundos). Para SessionStart, operaciones pesadas son aceptables. Para PreToolUse, que se dispara en cada herramienta, el script debe ser casi instantáneo:
# MAL: hook PreToolUse pesado
npm run build # 30 segundos cada vez que Claude usa una herramienta
# BIEN: hook PreToolUse ligero
grep -q "rm -rf" <<< "$COMMAND" && exit 2 # < 1ms
Comparación: settings.json vs Frontmatter
| Aspecto | settings.json | Frontmatter del subagent |
|---|---|---|
| Scope | Toda la sesión / proyecto | Solo ese subagent |
| Aplica a | Claude principal + todos los subagents | Solo el subagent específico |
| Dónde vive | .claude/settings.json | .claude/agents/my-agent.md |
| Cuándo usar | Reglas globales del proyecto | Reglas específicas del agente |
| Formato | JSON | YAML |
| Precedencia | Se ejecuta primero | Se ejecuta después |
| Distribución | Manual o via settings sync | Con el agent file |
Regla práctica: Si la regla es "nadie debe hacer X en este proyecto" → settings.json. Si la regla es "este agente específico no debe hacer Y" → frontmatter.
Ejercicios
Ejercicio 1: SessionStart básico (Fácil)
Crea un hook SessionStart que verifique si git está inicializado y si hay cambios sin commitear. Si hay más de 10 archivos modificados sin commit, reporta un warning (exit 1).
Ver solución
./scripts/git-check.sh:
#!/bin/bash
if ! git rev-parse --git-dir > /dev/null 2>&1; then
echo "WARNING: No es un repositorio git"
exit 1
fi
MODIFIED=$(git status --porcelain | wc -l | tr -d ' ')
if [ "$MODIFIED" -gt 10 ]; then
echo "WARNING: $MODIFIED archivos sin commitear"
echo "Considera hacer commit antes de empezar"
exit 1
fi
echo "Git OK: $MODIFIED archivos pendientes"
exit 0
En .claude/settings.json:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{ "type": "command", "command": "./scripts/git-check.sh" }
]
}
]
}
}
Ejercicio 2: PreToolUse — Bloquear rm recursivo (Fácil)
Crea un hook PreToolUse que bloquee (exit 2) cualquier comando Bash que contenga rm -rf seguido de /, ~, o ..
Ver solución
./scripts/block-rm.sh:
#!/bin/bash
INPUT=$(cat -)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$COMMAND" | grep -qE "rm\s+-rf\s+[/~.]"; then
echo "BLOCKED: rm -rf con ruta peligrosa detectado"
echo "Comando: $COMMAND"
exit 2
fi
exit 0
En .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "./scripts/block-rm.sh" }
]
}
]
}
}
Ejercicio 3: PreToolUse — Restricción de rutas por subagent (Medio)
Crea un subagent api-specialist.md que solo pueda editar archivos dentro de src/api/. Usa un hook PreToolUse en el frontmatter que bloquee cualquier Write o Edit fuera de ese directorio.
Ver solución
.claude/agents/api-specialist.md:
---
name: api-specialist
description: API endpoint specialist. Only modifies src/api/.
tools: Read, Write, Edit, Grep, Glob
hooks:
PreToolUse:
- matcher: "Write|Edit"
hooks:
- type: command
command: |
#!/bin/bash
INPUT=$(cat -)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty')
if [ -z "$FILE" ]; then
exit 0
fi
if ! echo "$FILE" | grep -q "^src/api/"; then
echo "BLOCKED: api-specialist solo puede editar src/api/"
echo "Intentó: $FILE"
exit 2
fi
exit 0
---
You are an API specialist. Implement and modify REST endpoints.
Only modify files in src/api/. Read any file for context.
La validación técnica garantiza que, incluso si el LLM intenta editar fuera de src/api/, el hook lo bloquea.
Ejercicio 4: PreToolUse — Validación condicional por branch (Medio)
Crea un hook PreToolUse que:
- En
main/master: bloquea (exit 2) cualquier Write/Edit - En branches de feature: permite todo pero loguea warnings para archivos de configuración
- En cualquier branch: bloquea acceso a
.env*
Ver solución
./scripts/branch-validator.sh:
#!/bin/bash
INPUT=$(cat -)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty')
if [ -z "$FILE" ]; then
exit 0
fi
if echo "$FILE" | grep -qE "^\.env"; then
echo "BLOCKED: Archivos .env son protegidos en todos los branches"
exit 2
fi
BRANCH=$(git branch --show-current 2>/dev/null)
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
echo "BLOCKED: No se permite editar archivos en branch $BRANCH"
echo "Crea un feature branch: git checkout -b feature/my-feature"
exit 2
fi
if echo "$FILE" | grep -qE "(config|settings|\.yaml|\.yml|\.toml)"; then
echo "WARNING: Editando archivo de configuración: $FILE"
echo "Branch: $BRANCH"
exit 1
fi
exit 0
Ejercicio 5: Múltiples hooks encadenados (Difícil)
Diseña una configuración settings.json con:
- SessionStart: verifica git y dependencias
- PreToolUse para Bash: bloquea comandos peligrosos
- PreToolUse para Write/Edit: restringe rutas
- PreToolUse universal (sin matcher): logging de todas las herramientas
Escribe la configuración JSON completa y los 4 scripts.
Ver solución
.claude/settings.json:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{ "type": "command", "command": "./scripts/hooks/session-setup.sh" }
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "./scripts/hooks/validate-bash.sh" }
]
},
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "./scripts/hooks/validate-paths.sh" }
]
},
{
"hooks": [
{ "type": "command", "command": "./scripts/hooks/log-tools.sh" }
]
}
]
}
}
./scripts/hooks/session-setup.sh:
#!/bin/bash
if ! git rev-parse --git-dir > /dev/null 2>&1; then
echo "WARNING: No git repo"
exit 1
fi
if [ -f "package.json" ] && [ ! -d "node_modules" ]; then
npm ci --silent
fi
echo "Setup complete"
exit 0
./scripts/hooks/validate-bash.sh:
#!/bin/bash
INPUT=$(cat -)
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$CMD" | grep -qiE "(rm -rf [/~.]|DROP DATABASE|DROP TABLE)"; then
echo "BLOCKED: $CMD"
exit 2
fi
exit 0
./scripts/hooks/validate-paths.sh:
#!/bin/bash
INPUT=$(cat -)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty')
if [ -z "$FILE" ]; then exit 0; fi
if echo "$FILE" | grep -qE "^\.env"; then
echo "BLOCKED: $FILE"
exit 2
fi
exit 0
./scripts/hooks/log-tools.sh:
#!/bin/bash
INPUT=$(cat -)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
echo "$TIMESTAMP | $TOOL" >> .claude/tool-log.txt
exit 0
Ejercicio 6: Debugging de hooks (Difícil)
Un desarrollador configuró este hook pero reporta que "no bloquea nada". Encuentra y corrige todos los bugs:
{
"hooks": {
"preToolUse": [
{
"match": "bash",
"hooks": [
{
"type": "cmd",
"command": "scripts/validate.sh"
}
]
}
]
}
}
#!/bin/bash
# scripts/validate.sh
COMMAND=$1
if [ "$COMMAND" == "rm -rf" ]; then
echo "Blocked"
fi
Ver solución
Hay 5 bugs:
preToolUse→ Debe serPreToolUse(PascalCase)match→ Debe sermatcherbash→ Debe serBash(PascalCase, nombre de herramienta de Claude Code)type: "cmd"→ Debe sertype: "command"COMMAND=$1→ Los hooks reciben input via stdin, no argumentos. Debe serINPUT=$(cat -)y luego parsear con jq- No hay exit code → El script no retorna
exit 2para bloquear; elechosolo imprime - Ruta sin
./→ Debe ser./scripts/validate.shpara ruta relativa
Configuración corregida:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "./scripts/validate.sh"
}
]
}
]
}
}
Script corregido:
#!/bin/bash
INPUT=$(cat -)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$COMMAND" | grep -q "rm -rf"; then
echo "BLOCKED: $COMMAND"
exit 2
fi
exit 0
Resumen
- SessionStart se dispara al arrancar cada sesión — ideal para setup automático de dependencias, verificación de base de datos, y migraciones
- PreToolUse se dispara antes de cada ejecución de herramienta — el guardián que valida, advierte, o bloquea operaciones
- Los hooks reciben input JSON via stdin con
tool_name,tool_input, y metadata de sesión - Exit codes: 0 = continuar, 1 = error (Claude decide), 2 = bloquear operación
- El campo
matcherfiltra por herramienta: nombre exacto, pipe-separated (Edit|Write), regex (.*), o sin matcher (captura todo) - Los hooks se configuran en settings.json (scope proyecto/usuario) o en el frontmatter de subagents (scope agente)
- Los hooks de settings.json se ejecutan primero, los de frontmatter después
- Múltiples hooks por evento se ejecutan en orden; si uno retorna exit 2, los siguientes no se ejecutan
- Mantén los hooks PreToolUse ligeros (< 2 segundos) — se ejecutan en cada invocación de herramienta
- Instala
jqpara parsear JSON en scripts bash — es esencial para hooks que inspeccionan input
Recursos Adicionales
- Claude Code Hooks (Anthropic Docs) — Documentación oficial de todos los eventos, matchers, y tipos de hooks
- Claude Code Settings — Ubicación y formato de settings.json
- Create Custom Subagents — Hooks en frontmatter YAML
- jq Manual — Referencia de jq para parseo de JSON en bash
- Claude Code CLI Reference — Referencia de herramientas y nombres para matchers
- Claude Code Best Practices — Buenas prácticas de seguridad y validación
- Bash Exit Codes — Referencia de exit codes en bash
- Claude Code Overview — Contexto general para entender el ciclo de vida de herramientas
Siguiente cápsula: En la cápsula 03 verás los hooks de reacción: PostToolUse para auto-lint después de ediciones, SubagentStart y SubagentStop para tracking del ciclo de vida de subagents, Stop para cleanup al final de la sesión, y PermissionRequest para flujos de aprobación custom. Pasas de prevenir (PreToolUse) a reaccionar (PostToolUse) — la otra mitad del sistema nervioso.