Cargando la bóveda…
Cargando la bóveda…
Los hooks son la pieza que pasa de 'IDE con IA' a 'agente que ejecuta tu workflow'. Aprendé a configurar PreToolUse, PostToolUse y SessionStart con casos reales: format-on-save, validación previa, notificaciones.
Los hooks son comandos shell que Claude Code ejecuta automáticamente en momentos específicos de su ciclo de vida. No son acciones que Claude decide; son comportamientos garantizados que el harness corre por vos.
La diferencia es clave: si le pedís a Claude "corré los tests después de cada cambio", lo va a hacer a veces. Si lo configurás como hook PostToolUse, se va a hacer siempre, sin pedírselo.
Los hooks van en ~/.claude/settings.json (global) o en .claude/settings.json (por proyecto). El segundo formato es el recomendado — viaja con tu repo:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "npx prettier --write \"$CLAUDE_FILE_PATHS\"" }
]
}
]
}
}Claude Code expone variables que podés usar en tus hooks:
$CLAUDE_FILE_PATHS — archivos tocados en la operación$CLAUDE_TOOL_NAME — nombre de la tool ejecutada$CLAUDE_TOOL_INPUT_JSON — el input completo de la toolEl más común. Después de cada Edit o Write, formatea el archivo automáticamente.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "echo \"$CLAUDE_FILE_PATHS\" | xargs -n1 npx prettier --write 2>/dev/null || true"
}
]
}
]
}
}El || true al final es importante: si Prettier falla (archivo sin formateador), no rompe la sesión.
Evitá que Claude ejecute cosas que no querés, sin tener que confirmarlas manualmente.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/block-dangerous.js"
}
]
}
]
}
}Y el script .claude/hooks/block-dangerous.js:
const input = JSON.parse(process.env.CLAUDE_TOOL_INPUT_JSON || '{}')
const cmd = input.command || ''
const banned = [
/rm\s+-rf\s+\//,
/git\s+push\s+--force.*main/,
/DROP\s+TABLE/i,
/:\(\)\{:\|:&\};:/, // fork bomb
]
const hit = banned.find((re) => re.test(cmd))
if (hit) {
console.error(`Comando bloqueado por hook: matched ${hit}`)
process.exit(2) // exit code 2 = bloqueo
}
process.exit(0)Un hook que sale con código 0 permite continuar. Código 2 bloquea la operación. Cualquier otro código se considera error y muestra el output al usuario.
Cuando abrís Claude Code en un repo, mostrale el estado de la última corrida de tests:
{
"hooks": {
"SessionStart": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "gh run list --limit 1 --json status,conclusion,workflowName | jq -r '.[] | \"CI último: \\(.workflowName) → \\(.conclusion // .status)\"'"
}
]
}
]
}
}El output del hook se inyecta en el contexto inicial. Claude lo ve antes de responderte el primer mensaje.
Si dejaste a Claude haciendo algo largo y querés que te avise cuando termina:
{
"hooks": {
"Stop": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude terminó\" with title \"Claude Code\" sound name \"Glass\"'"
}
]
}
]
}
}En Linux usá notify-send. En Windows, PowerShell con New-BurntToastNotification.
Si un hook no se ejecuta o tira error silencioso:
claude --debugTe muestra cada hook que dispara, su exit code, stdout y stderr. Es la única forma sana de iterar sobre hooks complejos.
La trampa más común es escribir un hook ambicioso (5 cosas encadenadas en un one-liner) y que falle silenciosamente. Empezá con echo "hook fired" para confirmar que dispara, después agregale lógica.
Una vez que dominás los hooks básicos, vale la pena mirar:
.claude/commands/ — para flujos repetitivos