Copy-paste configuraciones de hook de producción para la validación de precommit, escaneo de PII, salvaguardas de implementación y registro de sesión Hace seis meses, guardé una nota pegajosa en mi monitor, en la que se decía: “¿Habéis tirado la cinta? ¿Habéis revisado la rama? ¿Habéis mirado lo que ha borrado?” Cada sesión de Claude Code, las mismas verificaciones manuales. Cada vez que Claude editaba un archivo, cambiaba a otro terminal y ejecutaba el formateador. Cada vez que ejecutaba un comando Bash, miraba a la salida preguntándose si algo tocaba la producción. Cada vez que intentaba comprometerse, comprobaba que estaba en la rama derecha primero. La nota pegajosa no era el problema. El problema era que estaba haciendo el trabajo de un ordenador. patrón repetitivo que coincide en cada acción, cientos de veces al día. Entonces descubrí que Claude Code incendia eventos. Cada acción. llamadas de herramientas, ediciones de archivos, comandos de Bash, comienzos de sesión, finales de sesión. Todos ellos. Y puedes interceptar cualquiera de ellos con un script de shell. La nota pegajosa ha desaparecido.Aquí está lo que la sustituyó. El sistema de eventos que nadie habla Claude Code no es sólo una interfaz de chat que escribe código. Debajo de la superficie, ejecuta un ciclo de vida. El usuario envía una prompt. Claude decide qué herramienta usar. La herramienta ejecuta. Claude lee el resultado. Repetir hasta que haya terminado. A cada transición en ese ciclo, Claude Code lanza un evento y puede anexar un manipulador a cualquiera de ellos. Los dos acontecimientos que más importan: Su operador puede inspeccionar la llamada y devolver un veredicto JSON: permitirlo, negarlo, o permanecer en silencio y dejar que el flujo de permisos normal lo maneje. PostToolUse apaga después de que una llamada a la herramienta sea exitosa. Su manipulador obtiene la salida completa. Haz lo que quieras con él. Lint el archivo. Logue el comando. Envíe un webhook. La herramienta ya se ejecutó, por lo que esto es reactivo, no preventivo. Los operadores están registrados en Pueden ser comandos de shell, endpoints HTTP o prompts LLM. Yo uso comandos de shell porque son rápidos, debugables y no requieren infraestructura. .claude/settings.json La interfaz entera es: Claude Code conduce JSON a la stdin de su script. Su script escribe opcionalmente JSON a stdout. Eso es lo que es. Si su script sale sin salida, Claude Code procede normalmente. Guión 1: El guión de la prevención de desastres Lo escribí después de que Claude tratara de un directorio de construcción que sucedió a tener un simlink en mi carpeta de inicio. Nada malo sucedió porque lo capturé en el prompt de permiso. pero no debería tener que capturarlo. rm -rf #!/bin/bash # .claude/hooks/guard.sh # Blocks destructive commands before they execute CMD=$(jq -r '.tool_input.command // empty') [ -z "$CMD" ] && exit 0 # Patterns I never want to run unreviewed BLOCKED_PATTERNS=( 'rm -rf' 'rm -r /' 'DROP TABLE' 'DROP DATABASE' 'truncate ' '> /dev/sd' 'mkfs\.' 'dd if=' ) for pattern in "${BLOCKED_PATTERNS[@]}"; do if echo "$CMD" | grep -qi "$pattern"; then jq -n --arg reason "Blocked: command matches dangerous pattern '$pattern'" '{ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: $reason } }' exit 0 fi done Ocho patrones. Puede ser ochenta. El punto no es enumerar todos los comandos peligrosos. El punto es capturar los que usted personalmente casi ha ejecutado por accidente. Mi lista refleja mis cicatrices. El registro en : .claude/settings.json "PreToolUse": [ { "matcher": "Bash", "hooks": [{ "type": "command", "command": ".claude/hooks/guard.sh" }] } ] El es un regex en el nombre de la herramienta. Significa que este hook sólo apaga los comandos de Bash, no edita o lee archivos. matcher "Bash" Guión 2: El formaterio que nunca olvida Antes de este hook, mi flujo de trabajo fue: Claude edita el archivo, me doy cuenta de que la formatación está equivocada, le pido a Claude que lo corrija, Claude ejecuta el formater, reviso la versión formatada. #!/bin/bash # .claude/hooks/fmt.sh # Formats files immediately after Claude edits them TOOL=$(jq -r '.tool_name // empty') [ "$TOOL" != "Edit" ] && [ "$TOOL" != "Write" ] && exit 0 FP=$(jq -r '.tool_input.file_path // .tool_input.path // empty') [ -z "$FP" ] || [ ! -f "$FP" ] && exit 0 case "${FP##*.}" in js|ts|jsx|tsx|mjs) if [ -f node_modules/.bin/prettier ]; then node_modules/.bin/prettier --write "$FP" 2>/dev/null elif command -v npx &>/dev/null; then npx prettier --write "$FP" 2>/dev/null fi ;; rs) rustfmt "$FP" 2>/dev/null ;; py) command -v ruff &>/dev/null && ruff format "$FP" 2>/dev/null command -v ruff &>/dev/null && ruff check --fix "$FP" 2>/dev/null ;; go) gofmt -w "$FP" 2>/dev/null ;; css|scss) command -v stylelint &>/dev/null && stylelint --fix "$FP" 2>/dev/null ;; esac exit 0 Esta versión es más exhaustiva que el ejemplo mínimo que encontrarás en la documentación. Comprueba si el formater realmente existe antes de llamarlo. y para Python porque formatear y enlazar son cosas diferentes. ruff format ruff check --fix Regístralo como PostToolUse con un El archivo se formata antes de que Claude lo lea de vuelta o se mueva a la siguiente edición. "Edit|Write" El efecto compuesto es significativo. Más de una semana lo medí. Sin el gancho, pasaba un promedio de 4 minutos por sesión pidiendo a Claude para corregir los problemas de formatación. Con el gancho, cero. Más de 30 sesiones por semana, es decir, dos horas recuperadas. Título: El detector secreto Claude estaba debugando una integración de la API, leyendo un archivo de configuración que contenía una clave de API de prueba, e incluyó la clave en su respuesta. La clave era para un entorno de escenografía y se giró al día siguiente. Pero el principio me molestó. las salida de herramientas fluyen a través de la conversación, se cachegan, potencialmente se logran. No quiero credenciales en ese flujo. #!/bin/bash # .claude/hooks/secrets.sh # Scans tool output for credential-shaped strings OUTPUT=$(jq -r '.tool_output // empty') [ -z "$OUTPUT" ] && exit 0 ALERTS="" # AWS keys (AKIA...) echo "$OUTPUT" | grep -qE 'AKIA[0-9A-Z]{16}' && ALERTS="$ALERTS aws_key" # Generic API key patterns (long hex/base64 after common key names) echo "$OUTPUT" | grep -qiE '(api_key|apikey|secret_key|access_token|auth_token)["\x27: =]+[A-Za-z0-9+/]{20,}' && ALERTS="$ALERTS api_credential" # Private key headers echo "$OUTPUT" | grep -q 'BEGIN.*PRIVATE KEY' && ALERTS="$ALERTS private_key" # JWT tokens echo "$OUTPUT" | grep -qE 'eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.' && ALERTS="$ALERTS jwt_token" # Connection strings with passwords echo "$OUTPUT" | grep -qiE '(postgresql|mysql|mongodb|redis)://[^:]+:[^@]+@' && ALERTS="$ALERTS connection_string" if [ -n "$ALERTS" ]; then TS=$(date '+%H:%M:%S') TOOL=$(jq -r '.tool_name // unknown') echo "[$TS] SECRET_ALERT in $TOOL:$ALERTS" >> .claude/secret-alerts.log echo "WARNING: potential credential in $TOOL output ($ALERTS)" >&2 fi exit 0 Este no es un reemplazo para la gestión de secretos adecuada. Es un tripwire. Capta los patrones obvios: claves de acceso de AWS, cosas que parecen credenciales de API, bloques de clave privada, JWTs, cadenas de conexión de base de datos con contraseñas incorporadas. Cuando se quema, se enciende y imprime una advertencia a stderr. No bloquea nada. Bloquear un positivo falso interrumpiría tu flujo. Logar un positivo verdadero te da algo para investigar al final de la sesión. .claude/secret-alerts.log La revisión Una vez al día. La mayoría de las entradas son falsas positivas (test fixtures, ejemplos de documentación). Aproximadamente una vez a la semana, uno es real lo suficiente para justificar la rotación de una credencial o la actualización de un .gitignore. secret-alerts.log Título original: The Branch Cop corto, agudo, y nacido de un verdadero error. , pidió a Claude que empujara una corrección rápida, y se corrió antes de registrar lo que estaba sucediendo. El empuje pasó. CI era verde. No se hizo daño. Pero el compromiso no se suponía que aterrizara en el principal sin un PR. main git push origin main #!/bin/bash # .claude/hooks/no-push-main.sh # Prevents git push to main/master/production branches CMD=$(jq -r '.tool_input.command // empty') echo "$CMD" | grep -q 'git push' || exit 0 BRANCH=$(git branch --show-current 2>/dev/null) PROTECTED="main master production release" for b in $PROTECTED; do if [ "$BRANCH" = "$b" ]; then jq -n --arg branch "$BRANCH" '{ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: ("Push to " + $branch + " blocked. Create a feature branch first.") } }' exit 0 fi done # Also block force push to any branch if echo "$CMD" | grep -qE 'push.*(-f|--force)'; then jq -n '{ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: "Force push blocked. Use --force-with-lease if you must." } }' fi Dos guardias en un mismo guión. No empujando a las ramas protegidas. No empujando la fuerza a ninguna rama. La segunda regla es opcional, pero lo conservo porque los empujos de fuerza son el tipo de cosa que debería requerir que escriba el comando usted mismo, con plena conciencia de lo que está haciendo. Claude maneja la negación graciosamente. Ve la razón, sugiere crear una rama de características, y procede en la pista correcta. El gancho no interrumpe la sesión. Título: The Black Box Recorder Cada llamada de herramienta. cada entrada. cada salida. Timestamped y anexado a un archivo de registro. Este es el gancho que más a menudo recomiendo a los equipos porque responde a la pregunta que siempre le preguntas después de que algo vaya mal: "¿Qué exactamente hizo Claude?" #!/bin/bash # .claude/hooks/record.sh # Logs every tool call to a structured audit file TS=$(date '+%Y-%m-%d %H:%M:%S') TOOL=$(jq -r '.tool_name // "unknown"') SESSION=$(jq -r '.session_id // "unknown"') # Compact input (one line, no whitespace bloat) INPUT=$(jq -c '.tool_input // {}') # Truncate output to prevent log bloat (first 500 chars) OUTPUT=$(jq -r '.tool_output // empty' | head -c 500) # Tab-separated for easy parsing with awk/cut printf '%s\t%s\t%s\t%s\t%s\n' \ "$TS" "$SESSION" "$TOOL" "$INPUT" "$OUTPUT" \ >> .claude/session-log.tsv exit 0 Tab-separado, no JSON. elección deliberada. los archivos TSV son grabables, ordenables y cortables con herramientas Unix estándar. ¿Necesitas ver cada comando Bash a partir de hoy? grep "$(date +%Y-%m-%d)" .claude/session-log.tsv | grep "Bash" | cut -f4 ¿Necesito contar cuántas ediciones de archivos Claude hizo esta semana? grep -c "Edit\|Write" .claude/session-log.tsv El campo de salida se truncó a 500 caracteres para evitar archivos de registro de múltiples megabytes cuando Claude lee archivos grandes. Si necesita la salida completa, cambie a archivos de registro de sesión: reemplace el nombre de archivo con . .claude/logs/session-${SESSION}.tsv Para los equipos que necesitan un registro centralizado, sustituya el apéndice del archivo por un pegamento HTTP que apunte a su infraestructura de registro.La carga útil de JSON que Claude Code envía contiene todo lo que este script captura, además de contextos adicionales como el directorio de trabajo y la configuración de herramientas. El archivo de configuración combinada Los cinco manuscritos, unidos entre sí: { "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": ".claude/hooks/guard.sh" }, { "type": "command", "command": ".claude/hooks/no-push-main.sh" } ] } ], "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": ".claude/hooks/fmt.sh" } ] }, { "matcher": "", "hooks": [ { "type": "command", "command": ".claude/hooks/secrets.sh" }, { "type": "command", "command": ".claude/hooks/record.sh" } ] } ] } } Copia el JSON en Crea los cinco scripts en - Correr El Done. .claude/settings.json .claude/hooks/ chmod +x .claude/hooks/*.sh PreToolUse hooks funcionan en orden. ambos y Evaluar cada comando de Bash. Si uno de ellos devuelve una negación, el comando se bloquea. Los hooks de PostToolUse también se ejecutan en orden, pero no pueden bloquear nada; la herramienta ya se ejecutó. guard.sh no-push-main.sh El matcher vacío en la segunda entrada de PostToolUse significa y Quiero escanear la salida de Bash para credenciales y quiero registrar todo, independientemente del tipo de herramienta. secrets.sh record.sh Lo que aprendí corriendo estos seis meses Todo lo demás es seguro. El formateador es un ahorro de tiempo inmediato y mensurable. The formatter hook pays for itself on day one. Prefiero revisar diez falsas alarmas por semana que perder una credencial real. La revisión del archivo de registro dura 30 segundos. False positives in the secret scanner are fine. Cada unas semanas añado un patrón después de una casi-perdida. Ahora tiene ocho patrones. En seis meses tendrá veinte.Este es el punto.Acumula el conocimiento institucional de tu equipo sobre lo que nunca debería funcionar sin revisar. The guard script needs to evolve. rm -rf Pero cuando algo va mal, tener un registro completo de cada acción que Claude tomó convierte una investigación de dos horas en una captura de cinco minutos. The audit log is most valuable after incidents. Cada hook de PreToolUse agrega latencia a cada llamada de herramienta. Mis scripts de guardia y rama-cop se ejecutan en menos de 5ms cada uno. El formater es el más lento a 50-200ms dependiendo del tamaño del archivo, pero ejecuta PostToolUse para que no bloquee la tubería. Si su hook llama una API externa, utilice PostToolUse o acepte la latencia. Keep hooks fast. El Sticky Note ha desaparecido El monitoreo que antes vivía en mi cabeza ahora vive en cinco scripts de shell totalizando 150 líneas. Se ejecutan en cada llamada de herramienta sin mi participación. Atrapan cosas que me faltaría cuando estoy cansado al final de una larga sesión. Nunca olvidan comprobar la rama. Los asistentes de codificación de IA son poderosos.Pero el poder sin guardrails es solo un riesgo que aún no ha medido.Cinco scripts, diez minutos de configuración, y el riesgo cae a casi cero. La nota pegajosa ha sido reemplazada por algo mejor. Algo que no depende de mí recordándome de mirarlo.