Guía de Claude Code Hooks: controles seguros antes y después de trabajar
Guía práctica de Claude Code Hooks para principiantes: bloquear comandos peligrosos, guardar logs, formatear y ejecutar tests.
Claude Code Hooks son comprobaciones automáticas que se ejecutan antes o después de que Claude Code haga una acción. En lugar de recordar en cada prompt “no ejecutes comandos peligrosos”, “formatea el archivo” o “corre los tests”, puedes convertir esas reglas en parte del workflow del proyecto.
El modelo mental más útil para empezar es este: PreToolUse es el freno, PostToolUse es el mantenimiento, UserPromptSubmit es el registro de entrada y Stop es la revisión antes de cerrar. Hooks no sustituyen revisión humana, permisos ni CI. Sirven para que las comprobaciones repetidas sean más difíciles de olvidar.
Esta guía sigue la referencia oficial de Claude Code Hooks y la documentación oficial de settings. Para preparar bien el contexto del proyecto, mira también buenas prácticas de CLAUDE.md. Para una capa de seguridad más amplia, combínalo con la guía de permisos de Claude Code.
Cuatro eventos que conviene aprender primero
Claude Code tiene muchos eventos de Hooks, pero la mayoría de equipos obtiene valor con estos cuatro.
| Evento | Uso principal | Ejemplo |
|---|---|---|
UserPromptSubmit | Antes de que el prompt llegue a Claude | Guardar la solicitud, detectar secretos, añadir contexto ligero |
PreToolUse | Justo antes de ejecutar una herramienta | Bloquear comandos Bash destructivos o acciones de producción |
PostToolUse | Después de una herramienta exitosa | Ejecutar formatter, lint o tests relacionados |
Stop | Cuando Claude va a terminar la respuesta | Revisar conflictos, guardar resumen, recordar verificaciones pendientes |
Usa PreToolUse cuando la acción no debe ocurrir. Usa PostToolUse cuando la acción ya ocurrió y quieres limpiar o verificar. Usa UserPromptSubmit para entender la calidad de las peticiones. Usa Stop para no cerrar una sesión con un problema obvio.
Las reglas compartidas suelen vivir en .claude/settings.json. Los experimentos personales encajan mejor en .claude/settings.local.json, porque no deberían entrar al repositorio. En un equipo, documenta qué Hooks bloquean, cuáles solo registran información y quién los mantiene.
Configuración mínima para copiar
La siguiente configuración guarda prompts, bloquea comandos Bash peligrosos, ejecuta verificaciones después de editar archivos y guarda un resumen al final.
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/log-prompt.mjs"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/block-dangerous-command.mjs"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/run-quality-checks.mjs",
"timeout": 120
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/stop-summary.mjs"
}
]
}
]
}
}
El detalle importante es la estructura anidada. Primero eliges el evento, luego un grupo con matcher cuando aplica, y dentro defines handlers como type: "command". Todavía existen ejemplos antiguos con command directamente junto a matcher; no son el patrón que conviene copiar hoy.
Use case 1: bloquear comandos peligrosos con PreToolUse
El primer use case real es seguridad en Bash. Cuando el agente trabaja rápido, el momento crítico es antes de que se ejecute un comando destructivo. PreToolUse puede leer la entrada de Bash y devolver una decisión de denegación.
Guarda este archivo como .claude/hooks/block-dangerous-command.mjs.
import fs from "node:fs";
const input = JSON.parse(fs.readFileSync(0, "utf8") || "{}");
const command = String(input.tool_input?.command || "");
const denyPatterns = [
/rm\s+-rf\s+(\/|\*|\.|\$HOME)/i,
/cat\s+\.env(\.|$|\s)/i,
/printenv/i,
/aws\s+.*\s+delete-/i,
/gcloud\s+.*\s+delete/i,
/kubectl\s+delete\s+(namespace|deployment|secret)/i,
/DROP\s+DATABASE/i
];
const matched = denyPatterns.find((pattern) => pattern.test(command));
if (matched) {
console.log(JSON.stringify({
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: `Blocked by project hook: ${matched}`
}
}));
process.exit(0);
}
Esto no es un producto de seguridad completo. Las expresiones regulares pueden ser incompletas. Aun así, evitan errores muy comunes: mostrar .env, borrar carpetas amplias, eliminar recursos cloud, destruir recursos de Kubernetes o ejecutar SQL destructivo. Para producción, añade permisos de Claude Code, ramas protegidas, CI y aprobación humana.
Use case 2: registrar prompts con UserPromptSubmit
El segundo use case es observabilidad del prompt. Muchas sesiones fallidas no empiezan por un modelo débil, sino por una petición vaga: “arréglalo”, “límpialo”, “hazlo mejor”. Si guardas la solicitud, puedes analizar qué tipo de prompt produce buen trabajo y cuál produce ruido.
Guarda este archivo como .claude/hooks/log-prompt.mjs.
import fs from "node:fs";
import path from "node:path";
const input = JSON.parse(fs.readFileSync(0, "utf8") || "{}");
const dir = path.join(process.cwd(), ".claude", "hook-logs");
fs.mkdirSync(dir, { recursive: true });
const record = {
time: new Date().toISOString(),
event: input.hook_event_name,
cwd: input.cwd,
prompt: input.prompt || input.user_prompt || ""
};
fs.appendFileSync(
path.join(dir, "prompts.jsonl"),
JSON.stringify(record) + "\n",
"utf8"
);
Mantén este log local al principio. Un prompt puede contener nombres de clientes, URLs internas, tokens pegados por error o contexto comercial sensible. Añade .claude/hook-logs/ a .gitignore y define retención, acceso y responsable antes de convertirlo en workflow de equipo.
Use case 3: ejecutar format y test después de editar
El tercer use case es el ciclo de calidad diario. Después de que Claude escriba o edite un archivo, ejecuta formatter y una verificación pequeña. Así no tienes que repetir “formatea y prueba” en cada prompt.
Guarda este archivo como .claude/hooks/run-quality-checks.mjs.
import fs from "node:fs";
import { execFileSync } from "node:child_process";
const input = JSON.parse(fs.readFileSync(0, "utf8") || "{}");
const filePath = String(input.tool_input?.file_path || "");
const isSourceFile = /\.(js|jsx|ts|tsx|css|md|mdx|json)$/.test(filePath);
if (!isSourceFile) process.exit(0);
const run = (cmd, args) => {
try {
return execFileSync(cmd, args, {
cwd: process.cwd(),
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"]
});
} catch (error) {
return String(error.stdout || "") + String(error.stderr || "");
}
};
const messages = [];
messages.push(run("npx", ["prettier", "--write", filePath]));
if (/\.(js|jsx|ts|tsx)$/.test(filePath)) {
messages.push(run("npm", ["test", "--", "--runInBand"]));
}
console.log(JSON.stringify({
hookSpecificOutput: {
hookEventName: "PostToolUse",
additionalContext: messages.join("\n").slice(-4000)
}
}));
En un monorepo grande, no ejecutes toda la suite en cada escritura. Primero aplica format, luego tests relacionados, y deja los checks pesados para Hooks async o CI. También conviene recortar la salida que devuelves a Claude para no llenar el contexto con logs interminables.
Use case 4: revisar la sesión con Stop
Stop se dispara cuando Claude está por terminar. Es buen lugar para guardar un resumen o bloquear solo estados claramente incompletos, como conflictos de Git sin resolver.
import fs from "node:fs";
import { execFileSync } from "node:child_process";
const input = JSON.parse(fs.readFileSync(0, "utf8") || "{}");
if (input.stop_hook_active) {
process.exit(0);
}
let status = "";
try {
status = execFileSync("git", ["status", "--short"], {
cwd: process.cwd(),
encoding: "utf8"
});
} catch {
process.exit(0);
}
if (status.includes("UU ")) {
console.error("Git conflict remains. Resolve conflicts before finishing.");
process.exit(2);
}
fs.mkdirSync(".claude/hook-logs", { recursive: true });
fs.appendFileSync(
".claude/hook-logs/stop.jsonl",
JSON.stringify({
time: new Date().toISOString(),
lastAssistantMessage: input.last_assistant_message || "",
gitStatus: status.slice(0, 2000)
}) + "\n"
);
Ten cuidado con Stop. Si bloqueas demasiado, el agente puede quedar intentando terminar una y otra vez. Revisa stop_hook_active, mantén condiciones estrechas y empieza registrando más que bloqueando.
Pitfall: errores comunes y cómo evitarlos
El primer pitfall es creer que Hooks reemplaza permisos. Un command hook corre con los permisos de tu usuario del sistema. Si está mal escrito, puede leer secretos o borrar archivos. Valida entradas, evita traversal de rutas, no toques .env ni .git, y cita rutas correctamente.
El segundo pitfall es meter demasiada carga sincrónica. Formatear un archivo está bien. Ejecutar build, suite completa, tests de navegador y deploy después de cada edición suele ser excesivo. Mantén checks rápidos en PostToolUse, mueve lo pesado a async o CI.
El tercer pitfall es guardar logs sin política. Los logs ayudan a depurar, pero también pueden guardar información sensible. Define ubicación, retención, acceso y exclusión de Git antes de hacerlo estándar.
El cuarto pitfall es confundir matcher con lógica de negocio. matcher: "Bash" solo envía eventos Bash al handler. La decisión real debe revisar el comando completo dentro del script.
Nota de verificación estilo Masa
Al probar este patrón, la mayor mejora vino de devolver format y test a Claude mediante additionalContext. Claude puede corregir el siguiente paso sin que una persona copie logs al chat. El bloqueo de comandos peligrosos también aporta, no porque sea perfecto, sino porque frena los errores obvios en el instante correcto.
Para un equipo nuevo, lo introduciría en tres semanas: primero logs de prompts, luego format tras edición, y después una lista pequeña de patrones Bash prohibidos. Ese orden reduce fricción y construye confianza.
Si quieres empaquetarlo para tu equipo, la página de training de Claude Code reúne permisos, CLAUDE.md, Hooks y review workflow. Para trabajo individual, también puede convertirse en una checklist de Gumroad para iniciar proyectos con una base repetible.
Resumen
Claude Code Hooks son guardrails prácticos. Usa UserPromptSubmit para entender las solicitudes, PreToolUse para bloquear acciones riesgosas, PostToolUse para verificar cambios y Stop para no cerrar con problemas evidentes.
Empieza pequeño. Un Hook simple que nadie desactiva vale más que una automatización ambiciosa que estorba al segundo día.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Permission receipt para Claude Code: alcance, prueba y rollback
Patrón de permission receipt para Claude Code: acciones permitidas, aprobación, pruebas, rollback y CTA de ingresos.
Agent Harness seguro para Claude Code y Codex: permisos, verificacion y rollback
Diseña un Agent Harness seguro para Claude Code y Codex con permisos, plan, verificaciones y rollback.
Subagentes de Claude Code: guía práctica para delegar trabajo de forma segura
Guía práctica de subagentes en Claude Code para dividir artículos y código: reglas, prompts, riesgos y checklist.