Hook di Claude Code: automatizzare pre e post tool-use
Gli hook di Claude Code sono shell command eseguiti dalla harness su eventi pre/post tool: come configurarli in settings.json con esempi reali.

Gli hook di Claude Code sono uno dei modi piu’ diretti per forzare comportamenti deterministici nella sessione. Non sono logica che Claude decide se eseguire o meno: sono shell command che la harness lancia in risposta a eventi, prima o dopo l’uso di un tool, alla fine di un turno, all’arrivo di una notifica. Se un’azione deve succedere sempre, gli hook sono il posto giusto.
Cosa sono gli hook, in pratica
Un hook e’ una voce in ~/.claude/settings.json sotto la chiave hooks. Ogni voce associa un evento a uno o piu’ comandi shell, con un matcher che filtra su quale tool o pattern attivare la regola. La harness intercetta l’evento, valuta il matcher, esegue il comando e usa l’exit code per decidere se proseguire o bloccare.
Il punto chiave e’ proprio questo: la harness esegue gli hook, non Claude. Claude non puo’ “dimenticarsi” di farli partire, non puo’ decidere di saltarli, non li vede nemmeno nel contesto a meno che non ricevano output. E’ una differenza sostanziale rispetto a una skill o a uno slash command, dove la decisione di invocarli resta nel modello.
Eventi disponibili
PreToolUse— prima che un tool venga eseguito; puo’ bloccare l’azionePostToolUse— dopo un tool, utile per azioni derivate (format, lint, indicizzazione)UserPromptSubmit— quando l’utente invia un prompt; buono per iniettare contestoStop— fine turno, quando Claude smette di rispondereNotification— la harness emette una notifica (richiesta input, errore, ecc.)
Esempio concreto: auto-format al salvataggio
Il caso d’uso piu’ frequente: ogni volta che Claude modifica un file, lancia il formatter giusto in base all’estensione. Questo e’ il contenuto minimo di ~/.claude/settings.json che installa un hook PostToolUse su Edit e Write.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs -I{} sh -c 'case \"{}\" in *.py) black -q \"{}\" ;; *.js|*.ts|*.tsx|*.jsx) prettier -w \"{}\" ;; esac'"
}
]
}
]
}
}
La harness passa allo stdin dell’hook il payload JSON dell’evento; con jq si estrae il path del file, poi una case decide formatter. Funziona anche se Claude salva dieci file in fila: ognuno passa dall’hook.
Esempio 2: bloccare commit su file sensibili
Un hook PreToolUse su Bash puo’ ispezionare il comando e rifiutare se matcha un pattern pericoloso. Exit code diverso da zero blocca l’azione e rimanda un messaggio a Claude, che lo legge in contesto e puo’ rispondere di conseguenza.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.command' | grep -Eq 'git commit.*(\\.env|secrets|id_rsa)' && { echo 'Blocked: sensitive file in commit' >&2; exit 2; } || exit 0"
}
]
}
]
}
}
Con exit code 2 l’azione non parte e il messaggio stderr torna a Claude come feedback. Exit 0 lascia proseguire. Qualunque altro non-zero blocca senza messaggio strutturato.
Esempio 3: notifica desktop a fine task
Per task lunghi serve sapere quando la sessione si ferma senza tenere d’occhio il terminale. L’evento Stop e’ fatto apposta. Su macOS osascript, su Linux notify-send, su Windows BurntToast via PowerShell.
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Turno finito'"
}
]
}
]
}
}
Matcher: come si scrive
Il matcher e’ una regex sul nome del tool. Edit|Write cattura entrambi, .* cattura tutto, stringa vuota o assenza del campo equivale a “qualunque tool” per quell’evento. Filtri piu’ fini sui parametri del tool vanno fatti nel corpo dell’hook leggendo il JSON in stdin con jq, come negli esempi sopra.
Return code e comunicazione con Claude
- exit 0: azione consentita, stdout finisce nei log, Claude non vede nulla
- exit 2: azione bloccata, stderr torna a Claude come messaggio strutturato
- altri non-zero: azione bloccata, errore generico
Per questo, se si vuole che Claude capisca perche’ un’azione e’ stata negata, vale la pena usare exit 2 con un messaggio chiaro su stderr. Un hook che blocca in silenzio lascia il modello a indovinare.
Hook, skill o slash command?
Tre meccanismi diversi, tre livelli di controllo. Uno slash command (https://www.smartworkers.cloud/?p=3419) parte solo se l’utente lo digita. Una skill (https://www.smartworkers.cloud/?p=3423) parte se Claude decide che e’ pertinente al contesto. Un hook parte sempre, senza chiedere permesso. Regola pratica: se l’automazione deve essere garantita (format, audit, blocchi di sicurezza), e’ un hook. Se e’ un’azione opzionale che dipende dal contesto, e’ una skill o uno slash command.
Anti-pattern da evitare
- Hook che modifica file silenziosamente senza informare Claude: la prossima Edit puo’ fallire perche’ il contenuto non corrisponde piu’ a quello letto
- Comandi troppo lenti su
PostToolUse: rallentano ogni singolo tool, meglio spostarli aStop - Regex di matcher troppo larghe che catturano tool non voluti
- Hook che dipendono da variabili di ambiente non presenti nella shell della harness
- Dimenticare di rendere eseguibili gli script richiamati dai comandi
Gli hook sono un modo di spostare logica fuori dal modello e dentro la macchina. Dove la disciplina conta piu’ della flessibilita’, sono lo strumento giusto. Qual e’ il primo controllo che varrebbe la pena rendere automatico nel proprio setup?

Blogger dal 2001, Nativo Digitale, Developer.
Da 15 anni mi occupo di IT per una grande Azienda.
Lavoro per abbattere il Digital Divide.
Visita i miei altri progetti
sardiniamobility.com
www.cyberness.it