L’unico punto in cui puoi dire no: preToolUse

Un agente che sbaglia raramente va in crash: molto più spesso esegue con successo lo strumento sbagliato. Per questo il primo strato di gestione degli errori non è un try/catch nel tuo codice, ma un hook: un programma esterno che Copilot invoca in punti precisi del ciclo di vita e a cui passa un JSON su standard input con campi come sessionId, timestamp, cwd, toolName e toolArgs.

Gli hook esistono su tre superfici, con tre punti di configurazione diversi. Nella Copilot CLI stanno in file JSON sotto ~/.copilot/hooks/ (su Windows %USERPROFILE%\.copilot\hooks/) per la configurazione personale, e la CLI applica un ordine di precedenza fra le sorgenti: policy, utente, repository, plugin. Nel cloud agent di Copilot gli hook si dichiarano in un file NAME.json dentro .github/hooks/ del repository, e la documentazione è esplicita: il file di configurazione deve trovarsi sul branch predefinito per essere usato dal cloud agent. Nel Copilot SDK non scrivi file ma callback, per esempio onPreToolUse, passate alla creazione della sessione.

L’evento che può fermare qualcosa è preToolUse, eseguito prima che l’agente usi uno strumento. Hai due modi equivalenti per negare. Il primo è il codice di uscita: exit 2 di norma è solo un avviso, ma per preToolUse (e per permissionRequest) viene trattato come un rifiuto, e l’eventuale JSON su stdout viene fuso con la decisione di deny — la chiamata è negata anche se quel JSON dichiara permissionDecision: "allow". Il secondo è la forma strutturata: exit 0 e un JSON con permissionDecision impostato a deny più permissionDecisionReason, la motivazione mostrata all’utente. Lo stesso campo accetta ask, che non nega e non consente: in modalità interattiva chiede all’umano. C’è anche modifiedArgs, per sostituire gli argomenti dello strumento invece di bloccarlo.

{
  "version": 1,
  "hooks": {
    "preToolUse": [
      { "type": "command", "bash": ".github/hooks/deny-force-push.sh", "timeoutSec": 10 }
    ]
  }
}

Due comportamenti che smontano l’illusione del cancello

Qui sta la parte che l’esame ama, perché è controintuitiva. Primo: i timeout degli hook sono fail-open per ogni evento, incluso preToolUse e inclusi gli hook distribuiti da un amministratore come policy. Se l’hook non risponde entro timeoutSec viene ucciso, viene mostrato un avviso e l’elaborazione procede come se l’hook non fosse mai girato; per preToolUse la chiamata allo strumento continua attraverso il normale flusso dei permessi. Attenzione a non generalizzare: un hook che va in errore non è come un hook lento. Un exit non-zero diverso da 2 è fail-open per gli altri eventi, ma preToolUse è fail-closed e nega la chiamata riportando “Denied by preToolUse hook (hook errored)”. Solo i timeout sono esentati.

Secondo: postToolUse viene eseguito dopo che lo strumento è terminato con successo. Può restituire modifiedResult per sostituire il risultato, additionalContext per aggiungere indicazioni all’agente e suppressOutput, ma non può annullare nulla: l’effetto collaterale è già avvenuto. La confusione tipica da evitare è progettare la difesa su postToolUse e chiamarla controllo preventivo. Osserva, non impedisce.

Retry, rollback ed escalation

Il retry dipende dalla superficie. Nel cloud agent una sessione può sembrare bloccata e poi ripartire; se invece va in timeout, la strada indicata è disassegnare la issue e riassegnarla a Copilot, mentre per un lavoro innescato da un commento si riaggiunge lo stesso commento alla pull request. Se una richiesta viene bloccata dal firewall, un avviso compare nel corpo della pull request o in un commento, con l’indirizzo bloccato e il comando che ha provato a raggiungerlo: è un errore diagnosticabile, non un fallimento silenzioso.

Il rollback del cloud agent è di piattaforma, non di file: l’agente può fare push su un solo branch (quello della pull request o un nuovo branch copilot/), non può marcare la propria pull request come pronta per la review né approvarla o unirla, e per impostazione predefinita i workflow non partono finché il suo codice non è stato revisionato. Stop session termina la run di Actions e conserva i commit già pushati. Nella CLI il rollback è locale e si chiama rewind: premendo Esc due volte quando Copilot è inattivo si apre l’elenco degli snapshot creati man mano che l’agente risponde ai prompt. Da leggere con attenzione: il rewind riporta l’intero workspace allo stato dello snapshot scelto, quindi annulla tutte le modifiche successive, non solo quelle di Copilot ma anche le tue modifiche manuali e gli effetti dei comandi shell.

L’escalation, cioè il passaggio della decisione a una persona, ha forme diverse: nella CLI le richieste di permesso e i flag --allow-tool, --deny-tool, --available-tools, --excluded-tools (le regole di deny prevalgono sempre su quelle di allow, anche con --allow-all); nel cloud agent l’automation level del repository — Full control, Cautious, Balanced, Full automation — combinato con la confidence dichiarata per ogni azione e con il rationale che ne spiega il motivo, così che le modifiche sotto soglia restino come suggerimenti da accettare o rifiutare; nei GitHub Agentic Workflows il fatto che i permessi siano di sola lettura per default e che le scritture passino solo dai safe-outputs dichiarati nel frontmatter.

Dimostrare dopo che cosa è successo

La tracciabilità è la parte che si difende davanti a un audit. I session logs mostrano il ragionamento interno e gli strumenti usati; sul repository i commit del cloud agent sono attribuiti a Copilot con chi ha avviato il compito come co-author, sono firmati e ogni messaggio di commit contiene un link ai log della sessione. Nella CLI la storia sta in locale: le sessioni sono persistite in ~/.copilot/session-state/ con un database ~/.copilot/session-store.db, includono prompt, risposte, strumenti usati e file modificati, si riprendono con copilot --continue o --resume e si interrogano con i sottocomandi di /chronicle.

Lato organizzazione, gli eventi agentici arrivano nell’audit log con due campi da ricordare: actor_is_agent, che indica se l’attore è un agente AI e per gli eventi agentici è sempre true, e agent_session_id, presente quando l’evento è il risultato di una sessione di agente. Un amministratore vede inoltre il pannello Agent sessions sotto AI controls e può configurare lo streaming degli eventi delle sessioni. L’esame verifica soprattutto che tu non mescoli le superfici: chiedere “come si ritenta” o “dove si vede la traccia” ha risposte diverse per cloud agent, CLI, SDK e Agentic Workflows, e citare il campo di audit al posto del log di sessione (o viceversa) è l’errore che costa la domanda.