Hook di Claude Code: come bloccare un'azione anche quando il controllo non parte
Un hook di Claude Code è un controllo che gira in un punto fisso del ciclo di lavoro dell'agente, per esempio prima che esegua un comando, e può lasciarlo passare o fermarlo. Il problema è quello che succede quando il controllo stesso si rompe: per impostazione predefinita, molti errori di un hook non fermano niente, e l'azione che doveva essere bloccata parte lo stesso.
Il 9 ottobre 2026 il changelog di Claude Code ha aggiunto la 2.1.295, che introduce un'opzione proprio per questo caso. Il punto pratico è scrivere un controllo che non lasci passare un'azione solo perché è inciampato, e per arrivarci bisogna sapere come fallisce oggi un hook.
Cosa succede quando un hook di Claude Code non parte
Secondo la documentazione ufficiale degli hook, un hook di tipo command blocca un'azione soltanto se esce con codice 2, oppure se stampa un JSON di rifiuto con exit 0. Qualsiasi altro codice di uscita, compreso 1, è un errore non bloccante: l'azione procede e nella trascrizione compare un avviso hook error.
Lo stesso vale per i casi in cui l'hook non fa in tempo a decidere o non esiste. La tabella riassume i comportamenti descritti nella documentazione.
| Cosa va storto | Cosa fa Claude Code per impostazione predefinita |
|---|---|
| Lo script esce con codice 1 o altro codice diverso da 2 | Errore non bloccante, l'azione procede |
| Lo script esce con codice 2 | Su PreToolUse blocca la chiamata |
Un hook command supera il timeout | Su PreToolUse non blocca la chiamata |
| Un hook HTTP risponde non-2xx, non si connette o va in timeout | Errore non bloccante, l'esecuzione continua |
| Lo script è mancante o non eseguibile | Avviso non bloccante, la policy resta spenta |
Un percorso scritto male, un jq che manca sul computer, un server di controllo irraggiungibile: ognuno di questi spegne il controllo senza fermare nessuno, e di solito nessuno se ne accorge.
Cos'è un controllo che fallisce chiuso
Un controllo che fallisce chiuso è un controllo che, quando non riesce a decidere, nega l'azione invece di permetterla. L'opposto, fallire aperto, è il comportamento predefinito degli hook descritto sopra. La scelta giusta dipende da quanto costa sbagliare: un controllo di stile può fallire aperto, un controllo che protegge dati o produzione no.
Il changelog della 2.1.295 riporta la novità in questi termini: onFailure: "block" per gli hook command e HTTP, dove un hook che non riesce ad avviarsi, va in timeout o esce con un codice inatteso blocca l'azione invece di lasciarla passare. La pagina degli hook che ho letto oggi non documenta ancora questo campo, quindi prima di usarlo conviene controllare la sintassi esatta nella documentazione della propria versione.
Come scrivere un hook che fallisce chiuso anche senza la nuova opzione
Funziona con qualsiasi versione perché usa solo quello che la documentazione già garantisce: l'exit code 2 blocca sempre, su un evento che può bloccare. Basta far sì che ogni errore dello script diventi un exit 2.
#!/bin/bash
set -eo pipefail
trap 'echo "Controllo non riuscito, azione bloccata" >&2; exit 2' ERR
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
echo "rm -rf bloccato dall'hook" >&2
exit 2
fi
exit 0
Se jq manca o il JSON in ingresso è rotto, la sostituzione fallisce, scatta la trappola e il risultato è exit 2. Il comando non parte, e Claude riceve il motivo dallo stderr.
Il punto che nessuna riga di codice copre
Lo script in sé può essere sano e non essere mai chiamato. Se il percorso nel file di impostazioni è sbagliato o il file non è eseguibile, la documentazione parla di un avviso non bloccante, e lo script non gira mai. È il caso per cui è nata l'opzione onFailure, e nel frattempo la difesa è una prova: dopo ogni modifica, provocare apposta un'azione che l'hook deve fermare e vedere che si ferma.
Quali controlli devono fallire chiusi
Devono fallire chiusi i controlli che proteggono qualcosa che non si recupera, e possono fallire aperti gli altri. Nel mio lavoro il criterio è già scritto in un posto concreto: il prompt della routine che pubblica gli articoli di questo sito, nel file automazione-blog/cloud-agent/prompt-articolo-notizie.md, impone che se la build fallisce non si fa né commit né push, e che nel dubbio non si pubblica. Quella routine lavora senza nessuno davanti, ed è esattamente il caso in cui un controllo rotto non può diventare un via libera.
Lo stesso ragionamento vale per un agente che gira da solo, come spiega l'articolo su come far girare Claude Code in automatico. Se c'è una persona a guardare, un hook che salta si nota; se non c'è, nessuno vede l'avviso.
Cosa fare adesso, in ordine
Per rendere sicuri gli hook di Claude Code che già usate bastano cinque passaggi, da fare nell'ordine.
- Elencare gli hook che già usate in
.claude/settings.jsone in~/.claude/settings.json, e per ciascuno scrivere cosa succederebbe se non partisse. - Segnare quelli che proteggono dati, produzione o spesa: sono quelli da far fallire chiusi.
- Per ciascuno, aggiungere la trappola
exit 2nello script, oppureonFailure: "block"se la vostra versione di Claude Code lo supporta. - Aggiornare Claude Code alla 2.1.295 o successiva se volete l'opzione nativa.
- Rompere apposta ogni hook critico, per esempio rinominando lo script, e verificare che l'azione venga bloccata.
Per le regole che non richiedono logica, i permessi restano lo strumento più solido: l'articolo su come configurare Claude Code parte da lì, e quello su quali permessi dare all'AI in azienda ragiona su chi decide cosa un agente può toccare. Se invece vi interessa cosa leggere negli hook di un setup pubblico, c'è il pezzo su cosa installare di un setup pubblico, e per i controlli sul codice scritto dagli agenti controllare il codice scritto dall'AI. Chi vuole portare agenti con regole di questo tipo dentro un'azienda può partire dalla pagina sugli agenti AI.
Domande frequenti
Un hook può bloccare un comando dopo che è stato eseguito?
No. Un hook PostToolUse scatta quando lo strumento ha già girato, quindi non può annullare niente: la documentazione di Claude Code dice che in quel caso lo stderr viene mostrato a Claude. Per fermare un comando serve un hook PreToolUse, che scatta prima dell'esecuzione.
Dove si mettono gli hook di Claude Code?
Nei file di impostazioni: ~/.claude/settings.json per tutti i progetti, .claude/settings.json per un progetto condiviso nel repository, .claude/settings.local.json per quello che resta solo sul proprio computer. Gli hook possono stare anche nei file hooks/hooks.json di un plugin e nel frontmatter di skill e subagent.
Meglio un hook o una regola di permessi per vietare un comando?
Per un divieto assoluto la documentazione consiglia il sistema dei permessi, perché il filtro `if` degli hook è best-effort. L'hook serve quando la decisione richiede logica: leggere il comando, controllare un file, interrogare un servizio interno.
Come si prova che un hook blocchi davvero?
Si lancia a mano lo script, passandogli su stdin un JSON finto con un comando che deve essere bloccato, e si controlla che l'exit code sia 2. Poi si rompe apposta il controllo, per esempio rinominando lo script o togliendo jq, e si verifica che l'azione venga bloccata comunque.