Analisi completa del meccanismo Claude Code Hooks: cinque tipi di eventi, controllo dei codici di uscita e quattro configurazioni reali per rendere le azioni AI verificabili, intercettabili e automatizzabili.
Ogni volta che Claude Code legge un file, scrive codice o esegue un comando, un sistema di eventi lavora in background. Gli Hooks sono l'interfaccia per connettersi a questo sistema — puoi iniettare la tua logica in qualsiasi momento: eseguire automaticamente i linter, registrare le operazioni, bloccare azioni pericolose o attivare qualsiasi comando shell.
Questo articolo copre in modo completo il meccanismo degli Hooks, la configurazione e l'uso pratico.
Gli Hooks sono comandi shell configurati in settings.json che Claude Code esegue automaticamente quando si verificano eventi specifici.
L'analogia più diretta: i git hooks. Git può attivare script prima e dopo operazioni come commit e push. I Claude Code Hooks funzionano esattamente allo stesso modo — la differenza è che i punti di attivazione sono le chiamate agli strumenti dell'IA.
Perché è importante?
Più Claude Code diventa capace, più hai bisogno di un livello di controllo deterministico. Gli Hooks forniscono:
- Garanzie di esecuzione indipendenti dal prompt (Claude può ignorare le istruzioni, ma gli hook si eseguono sempre)
- Registri di operazioni verificabili
- Controlli di qualità automatizzati
| Tipo | Trigger | Uso tipico |
|---|---|---|
PreToolUse |
Prima di una chiamata a strumento | Bloccare operazioni pericolose, registrare l'intenzione |
PostToolUse |
Dopo una chiamata a strumento | Auto-lint, eseguire test |
PreCompact |
Prima della compattazione del contesto | Salvare uno snapshot dello stato attuale |
Notification |
Quando Claude invia una notifica | Avvisi desktop, messaggi Slack |
Stop |
Quando Claude termina di rispondere | Riassumere i log, attivare flussi successivi |
I più usati sono PreToolUse e PostToolUse, per intercettare e post-elaborare le chiamate agli strumenti.
Gli Hooks vanno in ~/.claude/settings.json (globale) o in .claude/settings.json nella root del progetto (livello progetto):
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npm run lint --silent"
}
]
}
]
}
}
Tre campi principali:
matcher: Regex che corrisponde ai nomi degli strumenti, determina quali chiamate attivano questo hook. "Write|Edit" si attiva quando si scrive o modifica un file. Lascia vuoto o usa ".*" per corrispondere a tutti gli strumenti.type: Al momento solo "command".command: Qualsiasi comando shell.Nomi degli strumenti da conoscere per scrivere i matcher:
| Nome strumento | Azione |
|---|---|
Write |
Scrivere un nuovo file |
Edit |
Modificare un file |
Bash |
Eseguire un comando shell |
Read |
Leggere un file |
Glob |
Ricerca file |
Grep |
Ricerca contenuto |
TodoWrite |
Aggiornare la lista dei task |
Durante l'esecuzione, i dati vengono passati come JSON tramite stdin:
{
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.rb",
"content": "..."
},
"tool_response": "..."
}
PreToolUse riceve tool_input (gli argomenti prima della chiamata). PostToolUse riceve anche tool_response (il valore di ritorno dello strumento).
Esempio di hook con logica condizionale:
#!/bin/bash
input=$(cat)
file=$(echo "$input" | jq -r '.tool_input.file_path')
# Eseguire rubocop solo sui file .rb
if [[ "$file" == *.rb ]]; then
rubocop "$file" --autocorrect-all --no-color
fi
Il codice di uscita dell'hook controlla il comportamento successivo di Claude Code:
| Codice di uscita | Significato |
|---|---|
0 |
Successo, continua l'esecuzione |
2 |
Blocca: annulla la chiamata allo strumento corrente, invia l'output stderr a Claude |
| Altri non-zero | Registra l'errore ma continua |
Il codice di uscita 2 è il più utile — consente di scrivere logica di intercettazione in PreToolUse per impedire a Claude di eseguire un'operazione e spiegargli il motivo.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash -c 'file=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path 2>/dev/null); [[ \"$file\" == *.rb ]] && rubocop -A \"$file\" --no-color -q || true'"
}
]
}
]
}
}
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c 'cmd=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .command); if echo \"$cmd\" | grep -qE \"rm.*/(migrations|seeds)\"; then echo \"Non è consentito eliminare le directory migrations o seeds\" >&2; exit 2; fi'"
}
]
}
]
}
}
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|Bash",
"hooks": [
{
"type": "command",
"command": "echo \"$(date '+%Y-%m-%d %H:%M:%S') $CLAUDE_TOOL_NAME: $(echo $CLAUDE_TOOL_INPUT | jq -c .)\" >> ~/.claude/audit.log"
}
]
}
]
}
}
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude ha completato\" with title \"Claude Code\"'"
}
]
}
]
}
}
Gli Hooks possono essere collocati in due posti:
Globale (~/.claude/settings.json): Regole comuni a tutti i progetti — log, notifiche.
Livello progetto (.claude/settings.json): Controlli specifici del progetto. Gli hooks del progetto e quelli globali si fondono e vengono eseguiti entrambi.
Per i progetti di team, fai commit del settings.json del progetto in git così tutti eseguono gli stessi hooks.
Passi per la risoluzione dei problemi quando un hook non si attiva:
write non è Writeecho "hook triggered" >> /tmp/hook.log per confermare l'attivazioneIl valore centrale degli Hooks è connettere il comportamento imprevedibile dell'IA agli standard di ingegneria deterministici. Claude può dimenticare di eseguire i test, ma un PostToolUse hook no. Claude può eliminare file per errore, ma un interceptor PreToolUse non lo permetterà.
Inizia con un hook: auto-lint dopo la scrittura di file. Quando funziona, aggiungine altri.