30 eventi di hook si attivano in una sessione Claude Code. Solo 3 possono rispondere.

L'elenco completo degli eventi di hook di Claude Code, quando si attiva ciascuno, quali 15 possono bloccare, e la regola di stdout che si mangia in silenzio l'output della maggior parte degli hook. Un riferimento sul campo costruito eseguendo hook in produzione su migliaia di sessioni di agenti.

Due guasti si ripresentano di continuo quando si collegano degli hook a Claude Code, e non si somigliano per niente.

Il primo: aggiungi un hook, non succede nulla. Nessun errore, nessun avviso, nessuna riga di log. L'hook semplicemente non viene mai eseguito.

Il secondo: l'hook viene chiaramente eseguito, se ne vedono gli effetti sul disco, ma il messaggio che stampa per l'agente non arriva mai. L'agente si comporta come se l'hook non avesse detto nulla.

Entrambi vengono dallo stesso posto. Il sistema di hook è più ampio e meno uniforme della manciata di eventi di cui parla la maggior parte degli articoli, e le regole su chi può parlare all'agente non sono quelle che immagineresti. Questo è il riferimento che avremmo voluto avere. Costruiamo AgentsRoom sopra questi hook, e tutto ciò che segue è citato dal riferimento ufficiale oppure misurato in produzione.

Gli eventi sono 30, non sei

La maggior parte delle guide copre PreToolUse, PostToolUse, UserPromptSubmit, Stop, Notification e SubagentStop. Quei sei sono reali, e portano gran parte del lavoro utile. Sono anche un quinto di ciò che esiste.

L'elenco completo, raggruppato per ciò che ciascun evento osserva:

GruppoEventi
SessioneSessionStart, SessionEnd, Setup
PromptUserPromptSubmit, UserPromptExpansion
StrumentiPreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch
PermessiPermissionRequest, PermissionDenied
TurnoStop, StopFailure
Sottoagenti e taskSubagentStart, SubagentStop, TaskCreated, TaskCompleted, TeammateIdle
ContestoPreCompact, PostCompact, InstructionsLoaded
AmbienteFileChanged, CwdChanged, ConfigChange
WorktreeWorktreeCreate, WorktreeRemove
InterfacciaNotification, MessageDisplay
Elicitation MCPElicitation, ElicitationResult

Diagramma cronologico dei 30 eventi di hook di Claude Code nell'ordine in cui si attivano durante una sessione di agente, da SessionStart a SessionEnd passando per UserPromptSubmit, PreToolUse, PostToolUse e Stop, con l'indicazione degli eventi che possono bloccare l'agente.

L'ordine di attivazione degli eventi in una singola sessione. Il blocco degli strumenti si ripete a ogni chiamata di strumento, e l'intero blocco del prompt si ripete a ogni turno.

Alcuni di questi eventi cambiano il modo di ragionare sul sistema. PostToolUseFailure esiste, quindi il ramo «lo strumento ha funzionato?» è un evento, non qualcosa che si deduce da un payload. PostToolBatch si attiva una volta sola dopo la risoluzione di un lotto di chiamate di strumenti in parallelo, ed è il posto giusto per lanciare un linter una volta invece che una volta per modifica. InstructionsLoaded si attiva alla lettura di CLAUDE.md, il che dà un punto di aggancio per verificare che l'agente abbia davvero caricato le regole che credi abbia caricato.

La regola di stdout che si mangia l'output della maggior parte degli hook

È la cosa più utile di questa pagina.

Con il codice di uscita 0, Claude Code analizza stdout in cerca di campi JSON di output. Ma se quello stdout venga mai mostrato all'agente dipende dall'evento, e le eccezioni sono un elenco corto. Dal riferimento:

Per la maggior parte degli eventi, stdout viene scritto nel log di debug ma non mostrato nella trascrizione. Le eccezioni sono UserPromptSubmit, UserPromptExpansion e SessionStart, dove stdout viene aggiunto come contesto che Claude può vedere e su cui può agire.

Tre eventi su trenta. Se fai echo "attenzione: questa migrazione è distruttiva" da un hook PostToolUse aspettandoti che l'agente lo legga, non lo leggerà mai. Il tuo testo è finito nel log di debug.

Esistono esattamente due modi per mettere del testo sotto gli occhi dell'agente da un qualsiasi altro evento:

  1. Uscire con 2 e scrivere su stderr. Con l'uscita 2, Claude Code ignora stdout e qualsiasi JSON al suo interno, e restituisce stderr all'agente come messaggio di errore.
  2. Uscire con 0 e stampare un oggetto JSON che porta hookSpecificOutput.additionalContext.

Nota l'asimmetria del primo punto. Uscita 0 significa che conta stdout e non stderr. Uscita 2 significa che conta stderr e stdout viene scartato del tutto. Invertire questi due è il motivo per cui un hook può sembrare perfettamente corretto e restare comunque muto.

Qualsiasi altro codice di uscita è un errore non bloccante. La trascrizione mostra un avviso <hook name> hook error con la prima riga di stderr, l'esecuzione continua, e lo stderr completo finisce nel log di debug.

Diagramma dei codici di uscita degli hook di Claude Code: l'uscita 0 invia il JSON di stdout all'agente solo su tre eventi, l'uscita 2 blocca l'azione e invia stderr all'agente, qualsiasi altro codice di uscita è un errore non bloccante scritto nel log di debug.

Quale canale raggiunge l'agente, per ciascun codice di uscita. Il percorso tratteggiato è quello che si dà per scontato e che non esiste.

Esattamente la metà può bloccare

Quindici eventi fermano l'azione con l'uscita 2. Quindici la ignorano e tirano dritto.

Possono bloccare: PreToolUse, PermissionRequest, UserPromptSubmit, UserPromptExpansion, Stop, SubagentStop, TeammateIdle, TaskCreated, TaskCompleted, ConfigChange, PostToolBatch, PreCompact, Elicitation, ElicitationResult, WorktreeCreate.

Non possono bloccare: PostToolUse, PostToolUseFailure, PermissionDenied, StopFailure, Notification, SubagentStart, SessionStart, Setup, SessionEnd, CwdChanged, FileChanged, PostCompact, WorktreeRemove, InstructionsLoaded, MessageDisplay.

La conseguenza pratica: una protezione va su PreToolUse, mai su PostToolUse. PostToolUse si attiva dopo che lo strumento è riuscito. Uscire con 2 lì non annulla la scrittura, stampa solo un errore mentre il danno è già sul disco. Se vuoi fermare un rm -rf, hai esattamente un posto in cui farlo.

Che PostToolBatch blocchi mentre PostToolUse no merita una seconda occhiata. Significa che un controllo a livello di lotto può ancora fermare il turno dopo che le modifiche parallele sono atterrate, ed è la cosa più vicina a un veto post-scrittura che il sistema offra.

I matcher sono esatti, finché all'improvviso non lo sono più

Il campo matcher cambia strategia di valutazione in base ai propri caratteri, e niente ti dice quale strada ha preso.

MatcherValutato come
"*", "", o assentecorrisponde a tutto
Solo lettere, cifre, _, -, spazi, ,, |stringa esatta, o elenco di stringhe esatte separate da | o ,
Qualsiasi altra cosaespressione regolare JavaScript non ancorata

Il tranello è «non ancorata». Il riferimento è esplicito: l'espressione viene testata con RegExp.prototype.test, che riesce su una corrispondenza in un punto qualsiasi del valore. Quindi Edit.* corrisponde a Edit e a NotebookEdit. Se intendevi un solo strumento, scrivi ^Edit$.

Due comportamenti dipendenti dalla versione, da conoscere prima di mettersi a debuggare la cosa sbagliata:

  • I separatori virgola e la tolleranza agli spazi richiedono Claude Code v2.1.191 o successivo.
  • I trattini sono entrati nell'insieme di caratteri della corrispondenza esatta in v2.1.195. Prima, un matcher come code-reviewer era trattato come una regex non ancorata, quindi si attivava anche per senior-code-reviewer.

In AgentsRoom restringiamo il nostro hook di attribuzione dei file con Write|Edit|MultiEdit|NotebookEdit, che resta sulla strada della stringa esatta e corrisponde a quei quattro strumenti e a nient'altro. Gli hook di ciclo di vita che installiamo non hanno alcun matcher, perché ci riguardano sempre.

Sei posti possono definire hook, e si fondono

L'istinto è cercare un ordine di precedenza. Non ce n'è uno, ed è proprio questa la parte interessante.

PosizioneAmbito
~/.claude/settings.jsontutti i tuoi progetti, locale alla tua macchina
.claude/settings.jsonun progetto, versionabile
.claude/settings.local.jsonun progetto, gitignorato da Claude Code
Managed policy settingstutta l'organizzazione, controllato dall'amministratore
hooks/hooks.json del pluginfinché il plugin è attivo
Frontmatter di skill o agentfinché il componente è attivo

Dal riferimento:

Le voci di hook si fondono tra i livelli di settings invece di sostituirsi a vicenda: i settings utente, di progetto e locali aggiungono i propri hook senza rimuovere quelli gestiti, e l'impostazione disableAllHooks non può disattivare gli hook gestiti dall'esterno dei settings gestiti.

Quindi un hook di progetto non sovrascrive mai un hook globale, gli si somma sopra. Sei sorgenti, tutte additive. Un formattatore PostToolUse definito nei tuoi settings utente e di nuovo nel progetto viene eseguito due volte per modifica, e l'unico sintomo è la sensazione che le cose siano lente.

Diagramma che mostra le sei posizioni dei settings di Claude Code che possono definire hook, tutte fuse in modo additivo in un unico insieme di hook invece di sostituirsi a vicenda.

Sei sorgenti, un solo insieme fuso. Qui niente sovrascrive niente.

Questo spiega anche perché .claude/settings.local.json è il posto giusto in cui uno strumento può installare un hook nel progetto di qualcun altro. È limitato al progetto, Claude Code lo gitignora, e viene caricato senza alcun flag da riga di comando. È lì che AgentsRoom scrive le proprie voci, così il .claude/settings.json versionato dell'utente non viene mai toccato e i suoi colleghi non ereditano mai un percorso specifico di una macchina.

Cosa ci ha insegnato eseguire hook in produzione

AgentsRoom installa hook in ogni progetto che apre, per tracciare lo stato degli agenti in modo deterministico e attribuire i file modificati all'agente giusto. Alcune cose si vedono solo a quella scala.

I nomi di evento sconosciuti vengono ignorati in silenzio. Non è documentato, e noi ci contiamo. Quando aggiungiamo un nuovo evento di ciclo di vita al nostro installer, gli utenti su una CLI più vecchia si ritrovano un settings.local.json che contiene un nome di evento di cui il loro binario non ha mai sentito parlare. Niente si rompe, niente avvisa, la voce viene saltata. È questo che rende sicuro rilasciare l'installer prima di una versione della CLI. Ed è anche, inevitabilmente, il motivo per cui un refuso produce silenzio totale invece di un errore.

agent_id è ciò che ti dice che sei dentro un sottoagente. Il campo è presente solo quando l'hook si attiva dentro una chiamata di sottoagente. Conta più di quanto sembri: Stop si attiva quando un sottoagente finisce il suo turno, non solo l'agente principale. Una regola ingenua del tipo «segna la sessione come conclusa su Stop» segna l'intera sessione come finita al primo ritorno di un sottoagente qualsiasi. Saltiamo gli eventi di fine turno che portano agent_id esattamente per questo motivo.

Non leggere transcript_path per il turno in corso. Il riferimento avverte che la trascrizione viene scritta in modo asincrono e può essere in ritardo rispetto alla conversazione in memoria, quindi i messaggi più recenti potrebbero non essere ancora lì quando il tuo hook si attiva. Stop e SubagentStop ricevono last_assistant_message proprio perché tu non debba mai correre dietro al file.

Gli hook sono l'unico segnale di stato affidabile. Prima degli hook, analizzavamo il PTY per capire se un agente stesse ragionando, aspettando o avesse finito. Questo si rompe nel momento in cui la CLI disegna attraverso il buffer di schermo alternativo del terminale, che è quello che fa /tui fullscreen. Gli hook si attivano in modo identico sotto qualsiasi renderer. Se stai costruendo qualcosa che osserva un agente dall'esterno, questo è il livello su cui costruire, e l'analisi del terminale resta al massimo un ripiego.

async: true non costa nulla. Un comando di hook può dichiarare async: true, e l'agente non lo aspetta. Il nostro hook fa una POST verso un endpoint locale con un tetto di 2 secondi e ritorna; la latenza del turno dell'agente non ne risente nemmeno quando l'applicazione ricevente è chiusa. Se il tuo hook si limita a osservare e non decide mai, rendilo async e smetti di pagarlo.

Non lasciare mai che un hook scriva spazzatura nel terminale. Il nostro script inghiotte ogni eccezione, anche al livello più alto. Un traceback non gestito da un hook non fallisce solo in silenzio: stampa uno stack trace Python nella sessione di terminale dell'utente, nel bel mezzo del suo lavoro.

Timeout

I valori di default sono generosi, con tre eccezioni che non lo sono:

Tipo di hookTimeout di default
command, http, mcp_tool600 s
prompt30 s
agent60 s
UserPromptSubmit (command, http, mcp_tool)30 s
MessageDisplay (command, http, mcp_tool)10 s
SessionEnd1,5 s condivisi tra tutti gli hook, alzati per allinearsi a un timeout per hook più lungo, fino a 60 s

Il budget di SessionEnd è quello che sorprende. È un budget condiviso, non un'allocazione per hook, quindi tre hook di pulizia si dividono 1,5 secondi a meno che non lo si alzi esplicitamente.

La versione breve

  • Esistono 30 eventi. Sei sono famosi.
  • stdout raggiunge l'agente solo su UserPromptSubmit, UserPromptExpansion e SessionStart. Ovunque altrove, usa l'uscita 2 con stderr, oppure additionalContext nel JSON.
  • 15 eventi bloccano con l'uscita 2, 15 la ignorano. Le protezioni vanno su PreToolUse.
  • I matcher sono stringhe esatte finché un carattere speciale non li trasforma in una regex non ancorata.
  • Sei sorgenti di settings si fondono in modo additivo. Niente sovrascrive niente.
  • Un nome di evento scritto male fallisce nel silenzio più completo.

Se preferisci vedere questi eventi attivarsi invece di ragionarci sopra, è esattamente quello che abbiamo costruito: AgentsRoom mostra ogni attivazione di hook per agente, per progetto, per esecuzione, su decine di agenti in parallelo e sessioni di sottoagenti. Gli hook che configuri nei tuoi settings continuano a funzionare esattamente come li hai scritti, perché esegue la vera CLI.

Continua a leggere

Scarica AgentsRoom

Eseguire i vostri agenti AI (Claude, Codex, Antigravity CLI, OpenCode, Aider, Grok Build, Mistral Vibe, Kimi Code) su tutti i vostri progetti, da una singola finestra.

GratisScarica AgentsRoom

App companion: monitora i tuoi agenti in movimento

Usa Claude, Codex, Antigravity CLI o un altro provider IA.

Installa l'estensione
Chrome Web Store

Invia bug e richieste direttamente nel tuo backlog pubblico.

Uno sguardo ad AgentsRoom in azione.

Multi-progetto
Multi-provider
Multi-agente
Stato in tempo reale
Diff e commit
App mobile
Anteprima live
Team di agenti
Test browser
Dev guidata da backlog
Libreria di prompt
Libreria di skill
Vedi tutte le funzionalità