Hooks e automazione locale controllata
Gli hook inseriscono controlli automatici nel momento in cui un agente usa uno strumento. Sono potenti perché agiscono sempre; proprio per questo devono avere condizioni strette, timeout e log comprensibili.
Cosa affronti, passo dopo passo
Il ciclo di vita di un evento
Un hook è una regola automatica collegata a un evento, per esempio prima o dopo l'uso di uno strumento. Per progettarlo devi disegnare la sequenza: che cosa lo attiva, quale informazione riceve, quale decisione produce e che cosa accade dopo.
Un evento nasce da un’azione richiesta. `PreToolUse` può consentire, bloccare o chiedere approvazione prima dell’esecuzione. Se lo strumento riesce interviene `PostToolUse`; se fallisce esiste l’evento distinto `PostToolUseFailure`. Ogni fase riceve un payload e deve produrre uno stato comprensibile.
Disegna prima la timeline. Se non sai se una regola deve prevenire un danno o verificare un risultato, non sai ancora in quale fase implementarla.
- Nomina l’evento e il payload minimo che lo descrive.
- Decidi se la regola deve prevenire l’azione o verificarne l’esito.
- Definisci condizioni, decisione ed exit code osservabili.
- Esegui lo strumento soltanto se il controllo preventivo consente.
- Registra l’esito e impedisci che l’hook riattivi se stesso.
Un agente può avviare il deploy di staging. Vuoi impedire branch non approvati e registrare l'esito senza automatizzare la produzione.
- Definisci l'evento `deploy.requested` con branch, commit, ambiente e identità richiedente; escludi token e variabili segrete dal payload.
- Nel pre-hook consenti soltanto `staging`, verifica che il commit esista e richiedi approvazione se il branch non segue la convenzione di release.
- Se consentito, lo strumento avvia il deploy e restituisce ID, commit distribuito e stato; un errore mantiene distinto `tool_failed` da `blocked`.
- Il post-hook legge quell'esito, interroga una sola volta il controllo di salute e registra `verified`, `failed` o `timeout`; non avvia mai un deploy di produzione.
Risultato: La timeline mostra chi ha chiesto cosa, perché l'azione è stata consentita e quale versione è stata realmente verificata. Un blocco non viene confuso con un guasto del deploy.
Pre-hook: proteggere il confine
Un pre-hook è una barriera posta prima di un'azione. Funziona bene quando la regola dipende da fatti che puoi misurare: percorso risolto, tipo di operazione, ambiente o presenza di un'approvazione valida.
Un pre-hook è adatto a bloccare scritture su file sensibili, comandi distruttivi o operazioni fuori ambito. La decisione dovrebbe dipendere da proprietà osservabili come percorso e tipo di azione, non da parole vaghe nel prompt.
Il messaggio di blocco deve spiegare quale regola è scattata e come procedere in sicurezza. Un blocco senza rimedio trasforma una protezione in un ostacolo opaco. Il pre-hook aggiunge un controllo, ma non sostituisce e non può aggirare la politica dei permessi.
Un workflow può eliminare artefatti sotto `/workspace/tmp/build-42`, ma non deve toccare sorgenti, home directory o percorsi derivati da variabili vuote.
- Il pre-hook riceve operazione e bersaglio, rifiuta input vuoto o glob non risolto e calcola il percorso canonico senza eseguire la cancellazione.
- Confronta il bersaglio con la radice esplicita `/workspace/tmp` e vieta la radice stessa; consente soltanto un discendente non simbolico identificato per quella build.
- Mostra una preview dei file e richiede l'ID dell'approvazione per quantità o dimensioni oltre la soglia; non stampa il contenuto dei file.
- Prova directory valida, `../src`, collegamento verso home e variabile vuota. Solo il primo caso deve raggiungere lo strumento; gli altri spiegano il percorso risolto e il rimedio.
Risultato: L'automazione pulisce il proprio artefatto senza trasformare un errore di percorso in una cancellazione ampia. I casi negati sono comprensibili e testabili.
Post-hook: osservare ciò che è già avvenuto
Un post-hook lavora su ciò che è davvero accaduto. Può rendere immediato un controllo ripetitivo, ma non dovrebbe nascondere una correzione sostanziale o far sembrare riuscita un'azione che invece ha lasciato errori.
Dopo una modifica riuscita, un hook può formattare il file, eseguire un controllo mirato, sostituire l’output mostrato al modello o registrare l’evento. Il punto cruciale è temporale: lo strumento ha già agito, quindi un `PostToolUse` non può annullare una scrittura o prevenire una chiamata di rete già avvenuta.
Per impedire un’azione usa `PreToolUse`. Per reagire a un errore dello strumento usa `PostToolUseFailure`. In entrambi i casi limita il controllo ai file coinvolti e restituisci contesto, durata e messaggio senza nascondere all’agente la causa reale.
L'agente cambia `src/orders/route.ts`. Il progetto vuole formattazione coerente e un controllo tipi del solo pacchetto API.
- Il post-hook si attiva solo dopo una scrittura riuscita su file `.ts` del pacchetto API e riceve l'elenco esatto dei file toccati.
- Esegue il formatter su quei file, registra se il diff è cambiato e non apre altri file per una pulizia generale.
- Lancia `npm run typecheck --workspace api` con timeout definito; conserva exit code, durata e prime righe diagnostiche prive di dati sensibili.
- Se il controllo fallisce, restituisce `written_but_unverified` e il messaggio; non modifica tipi o test in silenzio e non annulla la scrittura dell'utente.
Risultato: Hai una formattazione ripetibile e una prova pertinente. Un errore di tipo resta visibile e attribuito al controllo successivo, senza essere confuso con il fallimento della scrittura.
Idempotenza, ricorsione e fallimento
Un hook affidabile deve comportarsi bene anche quando viene ripetuto, si sovrappone a un'altra esecuzione o incontra un servizio in errore. Questi casi non sono eccezioni remote: sono il normale costo dell'automazione.
Un hook idempotente può essere ripetuto senza moltiplicare effetti. Un formatter lanciato da `PostToolUse` non genera automaticamente un nuovo evento; il ciclo nasce se lo script provoca proprio l’evento che osserva o chiede nuove chiamate allo stesso strumento. Nomina quell’evento e usa un marcatore di esecuzione o un comando che non lo riattivi.
Definisci timeout e politica di errore. Per una protezione critica può essere corretto fallire chiuso; per una metrica non essenziale può essere preferibile registrare il problema e proseguire.
Dopo una modifica in `docs/`, un hook aggiorna `docs/index.json`. Il primo prototipo si riattiva quando salva proprio l'indice e duplica le voci.
- Cambia il generatore: legge i documenti sorgente, ordina le voci e riscrive l'intero indice soltanto se il contenuto calcolato è diverso.
- Escludi `docs/index.json` dai file che attivano l'hook e aggiungi un lock breve per impedire a due salvataggi simultanei di scrivere insieme.
- Esegui due volte lo stesso evento: il primo aggiornamento produce il nuovo indice, il secondo non cambia byte né aggiunge voci. Poi simula un processo interrotto e verifica il rilascio del lock.
- Se la generazione fallisce, conserva l'indice precedente, marca la documentazione come non verificata e mostra il comando manuale; non pubblicare un JSON parziale.
Risultato: Retry e salvataggi ravvicinati producono un solo indice valido. Il file generato non riattiva il proprio hook e un guasto lascia disponibile l'ultima versione completa.
Protezione e formattazione
Task Notes contiene `.env` e file Python formattabili.
PRE write: se path termina con .env → blocca e spiega
TOOL write: applica la modifica autorizzata
POST write: se path termina con .py → formatta quel file
LOG: evento, decisione, durata, exit code; mai il contenuto del segreto