SpyBara
Go Premium

hooks.md 2026-10-08 22:58 UTC to 2026-10-09 22:01 UTC

This page contains 124 additions and 35 deletions.

2026
Thu 1 23:59 Fri 2 22:59 Sat 3 23:57 Sun 4 23:58 Mon 5 23:58 Tue 6 23:59 Wed 7 23:59 Thu 8 22:58 Fri 9 23:02

Riferimento dei hooks

Riferimento per gli eventi dei hook di Claude Code, schema di configurazione, formati JSON di input/output, codici di uscita, hook asincroni, hook HTTP, hook di prompt e hook degli strumenti MCP.

Gli hook sono comandi shell definiti dall'utente, endpoint HTTP, chiamate agli strumenti MCP, prompt LLM o subagent che si eseguono automaticamente in punti specifici del ciclo di vita di Claude Code. Claude Code attiva gli stessi eventi di hook ovunque venga eseguito: sessioni nel terminale, estensioni IDE, l'app Desktop e le sessioni cloud. Utilizzare questo riferimento per cercare schemi di eventi, opzioni di configurazione, formati JSON di input/output e funzionalità avanzate come hook asincroni, hook HTTP e hook degli strumenti MCP.

Un plugin può anche registrare hook come funzioni JavaScript che Claude Code chiama nel proprio processo, che possono disegnare nell'interfaccia e agire anche su eventi. Un plugin che lo fa è un mod e questi hook di funzione sono trattati in Reagire agli eventi piuttosto che qui. Gli hook su questa pagina continuano a funzionare insieme ai mod.

Ciclo di vita dei hook

Claude Code esegue i hook in punti specifici durante una sessione. Quando un evento si attiva e un matcher corrisponde, Claude Code passa il contesto JSON dell'evento al gestore del hook. Per i hook di comando, l'input arriva su stdin. Per i hook HTTP, arriva come corpo della richiesta POST. Il gestore può quindi ispezionare l'input, intraprendere un'azione e facoltativamente restituire una decisione.

Gli eventi si dividono in tre cadenze:

  • per sessione: SessionStart e SessionEnd
  • per turno: UserPromptSubmit, Stop e StopFailure
  • ad ogni chiamata dello strumento all'interno del ciclo agentico: PreToolUse e PostToolUse, ad eccezione delle chiamate EndConversation, che saltano entrambe
Diagramma del ciclo di vita dei hook che mostra Setup facoltativo che alimenta SessionStart, quindi un ciclo per turno contenente UserPromptSubmit, UserPromptExpansion per slash commands, il ciclo agentico annidato (PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure, PostToolBatch, SubagentStart/Stop, TaskCreated, TaskCompleted), e Stop o StopFailure, seguito da TeammateIdle, PreCompact, PostCompact e SessionEnd, con Elicitation e ElicitationResult annidati all'interno dell'esecuzione dello strumento MCP, PermissionDenied come ramo laterale di PermissionRequest per i rifiuti in modalità automatica, WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, FileChanged e DirectoryAdded come eventi asincroni autonomi, PreModelSwitch come evento sequenziale autonomo che viene eseguito prima di un cambio di modello richiesto, PostModelSwitch come evento asincrono autonomo che viene eseguito dopo il cambio del modello della sessione, e MessageDisplay come evento di sola visualizzazione che viene eseguito mentre il testo del messaggio dell'assistente viene trasmesso in streaming
<img src="https://mintcdn.com/claude-code/x7pO8l4XcvAXCoVc/images/hooks-lifecycle-dark.svg?fit=max&auto=format&n=x7pO8l4XcvAXCoVc&q=85&s=c9b3d88487335f58cce0b52e2f9e7531" className="hidden dark:block" alt="Diagramma del ciclo di vita dei hook che mostra Setup facoltativo che alimenta SessionStart, quindi un ciclo per turno contenente UserPromptSubmit, UserPromptExpansion per slash commands, il ciclo agentico annidato (PreToolUse, PermissionRequest, PostToolUse, PostToolUseFailure, PostToolBatch, SubagentStart/Stop, TaskCreated, TaskCompleted), e Stop o StopFailure, seguito da TeammateIdle, PreCompact, PostCompact e SessionEnd, con Elicitation e ElicitationResult annidati all'interno dell'esecuzione dello strumento MCP, PermissionDenied come ramo laterale di PermissionRequest per i rifiuti in modalità automatica, WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, FileChanged e DirectoryAdded come eventi asincroni autonomi, PreModelSwitch come evento sequenziale autonomo che viene eseguito prima di un cambio di modello richiesto, PostModelSwitch come evento asincrono autonomo che viene eseguito dopo il cambio del modello della sessione, e MessageDisplay come evento di sola visualizzazione che viene eseguito mentre il testo del messaggio dell'assistente viene trasmesso in streaming" width="520" height="1336" data-path="images/hooks-lifecycle-dark.svg" />

La tabella seguente riassume quando si attiva ogni evento. La sezione Hook events documenta lo schema di input completo e le opzioni di controllo della decisione per ognuno.

Evento Quando si attiva
SessionStart Quando una sessione inizia o riprende
Setup Quando avvii Claude Code con --init-only, o con --init o --maintenance in modalità -p. Per la preparazione una tantum in CI o script
UserPromptSubmit Quando viene inviato un prompt, prima che Claude lo elabori. Si attiva anche nei turni che Claude Code avvia autonomamente
UserPromptExpansion Quando un comando digitato dall'utente si espande in un prompt, prima che raggiunga Claude. Può bloccare l'espansione
PreToolUse Prima che una chiamata a uno strumento si esegua. Può bloccarla
PermissionRequest Quando una chiamata a uno strumento necessita di una decisione di autorizzazione
PermissionDenied Quando la modalità automatica nega una chiamata a uno strumento, inclusi i rifiuti senza un verdetto del classificatore. Utilizza JSON hookSpecificOutput.retry: true per indicare al modello che può riprovare la chiamata allo strumento negata. Claude Code ignora retry quando il classificatore non ha prodotto alcun verdetto
PostToolUse Dopo che una chiamata a uno strumento ha successo
PostToolUseFailure Dopo che una chiamata a uno strumento fallisce
PostToolBatch Dopo che un intero batch di chiamate a strumenti paralleli si risolve, prima della prossima chiamata al modello
Notification Quando Claude Code invia una notifica
MessageDisplay Mentre il testo del messaggio dell'assistente viene visualizzato
SubagentStart Quando un subagente viene generato
SubagentStop Quando un subagente termina
TaskCreated Quando un'attività viene creata tramite TaskCreate
TaskCompleted Quando un'attività viene contrassegnata come completata
Stop Quando Claude finisce di rispondere
StopFailure Quando il turno termina a causa di un errore API
TeammateIdle Quando un compagno di squadra di un team di agenti sta per diventare inattivo
InstructionsLoaded Quando un file CLAUDE.md o .claude/rules/*.md viene caricato nel contesto. Si attiva all'inizio della sessione e quando i file vengono caricati in modo pigro durante una sessione
ConfigChange Quando un file di configurazione cambia durante una sessione
CwdChanged Quando la directory di lavoro cambia, ad esempio quando Claude esegue un comando cd. Utile per la gestione reattiva dell'ambiente con strumenti come direnv
DirectoryAdded Quando una directory di lavoro viene aggiunta a metà sessione tramite /add-dir o la richiesta di controllo SDK register_repo_root
FileChanged Quando un file osservato cambia su disco. Il campo matcher specifica quali nomi di file osservare
WorktreeCreate Quando un worktree viene creato tramite --worktree, isolation: "worktree", o per una sessione in background. Sostituisce il comportamento git predefinito
WorktreeRemove Quando viene rimosso un worktree creato da un hook WorktreeCreate
PreCompact Prima della compattazione del contesto
PostCompact Dopo che la compattazione del contesto è completata
PreModelSwitch Prima che Claude Code applichi un cambio di modello che hai richiesto tu o un client. Può bloccare il cambio
PostModelSwitch Dopo che il modello della sessione cambia, inclusi i cambiamenti che Claude Code effettua autonomamente, come il ripristino del modello quando riprendi una sessione
Elicitation Quando un server MCP richiede input dell'utente durante una chiamata a uno strumento
ElicitationResult Dopo che un utente risponde a un'elicitazione MCP, prima che la risposta venga inviata al server
SessionEnd Quando una sessione termina

Come si risolve un hook

Per vedere come l'evento, il matcher e il gestore si combinano insieme, considerare questo hook PreToolUse che blocca i comandi shell distruttivi.

Il matcher si restringe alle chiamate dello strumento Bash e la condizione if si restringe ulteriormente ai sottocomandi Bash che corrispondono a rm *, quindi block-rm.sh viene eseguito solo quando entrambi i filtri corrispondono:

{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
"args": []
}
]
}
]
}
}

Lo script legge l'input JSON da stdin, estrae il comando e restituisce una permissionDecision di "deny" se contiene rm -rf. Salvarlo in .claude/hooks/block-rm.sh nel progetto e renderlo eseguibile con chmod +x .claude/hooks/block-rm.sh in modo che Claude Code possa eseguirlo:

#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0  # nessuna decisione; il flusso di autorizzazione normale si applica
fi

Questo script, come gli altri esempi Bash su questa pagina che analizzano l'input JSON, utilizza jq, quindi installare jq e assicurarsi che sia nel PATH prima di provarli.

Supponiamo che Claude Code decida di eseguire Bash "rm -rf /tmp/build" rispetto alla configurazione macOS/Linux. Ecco cosa accade:

Diagramma della risoluzione del hook: PreToolUse si attiva, il matcher controlla la corrispondenza di Bash, quindi la condizione if controlla la corrispondenza di Bash(rm *). Se entrambi corrispondono, il comando del hook viene eseguito e restituisce permissionDecision deny, quindi la chiamata dello strumento viene bloccata e Claude Code continua. Se uno dei controlli non corrisponde, l'hook viene saltato e la chiamata dello strumento è autorizzata a procedere. Diagramma della risoluzione del hook: PreToolUse si attiva, il matcher controlla la corrispondenza di Bash, quindi la condizione if controlla la corrispondenza di Bash(rm *). Se entrambi corrispondono, il comando del hook viene eseguito e restituisce permissionDecision deny, quindi la chiamata dello strumento viene bloccata e Claude Code continua. Se uno dei controlli non corrisponde, l'hook viene saltato e la chiamata dello strumento è autorizzata a procedere.
1

L'evento si attiva

L'evento PreToolUse si attiva. Claude Code invia l'input dello strumento come JSON su stdin al hook:

{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
2

Il matcher controlla

Il matcher "Bash" corrisponde al nome dello strumento, quindi questo gruppo di hook si attiva. Se si omette il matcher o si utilizza "*", il gruppo si attiva ad ogni occorrenza dell'evento.

3

La condizione if controlla

La condizione if "Bash(rm *)" corrisponde perché rm -rf /tmp/build è un sottocomando che corrisponde a rm *, quindi questo gestore viene eseguito. Se il comando fosse stato npm test, il controllo if avrebbe fallito e block-rm.sh non sarebbe mai stato eseguito, evitando il sovraccarico di spawn del processo. Il campo if è facoltativo; senza di esso, ogni gestore nel gruppo corrispondente viene eseguito.

4

Il gestore del hook viene eseguito

Lo script ispeziona il comando completo e trova rm -rf, quindi stampa una decisione su stdout:

{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
}
}

Se il comando fosse stato una variante più sicura di rm come rm file.txt, lo script avrebbe raggiunto exit 0 invece. Il codice di uscita 0 senza output significa che l'hook non ha alcuna decisione da segnalare, quindi la chiamata dello strumento continua attraverso il normale flusso di autorizzazione. L'hook può negare la chiamata, ma rimanere in silenzio non la approva.

5

Claude Code agisce sul risultato

Claude Code legge la decisione JSON, blocca la chiamata dello strumento e mostra a Claude il motivo.

La sezione Configuration seguente documenta lo schema completo, e ogni sezione hook event documenta quale input riceve il comando e quale output può restituire.

Configurazione

Gli hook sono definiti in file di impostazioni JSON. La configurazione ha tre livelli di annidamento:

  1. Scegli un evento hook a cui rispondere, come PreToolUse o Stop
  2. Aggiungi un gruppo matcher per filtrare quando si attiva, come "solo per lo strumento Bash"
  3. Definisci uno o più handler hook da eseguire quando corrisponde

Vedi Come si risolve un hook sopra per una procedura dettagliata completa con un esempio annotato.

Posizioni degli hook

Il luogo in cui definisci un hook determina il suo ambito:

Posizione Ambito Condivisibile
~/.claude/settings.json Tutti i tuoi progetti No, locale sulla tua macchina
.claude/settings.json Singolo progetto Sì, può essere committato nel repo
.claude/settings.local.json Singolo progetto No, gitignored quando Claude Code salva un'impostazione in esso
Impostazioni di policy gestite A livello di organizzazione Sì, controllato dall'amministratore
Plugin hooks/hooks.json Quando il plugin è abilitato Sì, incluso nel plugin
Skill frontmatter Il resto della sessione una volta che lo skill è invocato. Vedi Hook in skill e agenti Sì, definito nel file dello skill
Subagent frontmatter Mentre quel subagent è in esecuzione Sì, definito nel file del subagent

Le sessioni cloud non leggono il tuo ~/.claude/settings.json locale. In un ambiente self-hosted, Claude Code esegue anche gli hook che l'operatore ha seminato da ~/.claude/ dell'host runner, e esegue gli hook nel file di impostazioni gestite dell'immagine runner quando quel file è tra le fonti gestite che Claude Code applica, il che per impostazione predefinita significa solo quando né le impostazioni gestite dal server né una policy Claude Code consegnata da MDM forniscono il livello gestito. Vedi cosa viene trasferito dalla tua configurazione per quali file di impostazioni e plugin, e quindi quali hook, raggiungono una sessione cloud.

Per i dettagli sulla risoluzione dei file di impostazioni, vedi settings.

Gli hook dai file di impostazioni, dalle impostazioni di policy gestite e dai plugin vengono eseguiti anche all'interno di subagenti. Quando un subagent chiama uno strumento, gli eventi dello strumento come PreToolUse e PostToolUse attivano gli stessi hook configurati della conversazione principale, e l'input contiene i campi di input comuni agent_id e agent_type che identificano il subagent.

Gli amministratori possono utilizzare allowManagedHooksOnly nelle impostazioni gestite per limitare quali hook vengono eseguiti:

  • I tuoi hook utente, progetto, locale e plugin sono bloccati. Gli hook dai plugin forzatamente abilitati nelle impostazioni gestite enabledPlugins sono esenti
  • Claude Code restringe anche le tue impostazioni statusLine, fileSuggestion, e subagentStatusLine alle impostazioni gestite
  • Claude Code disabilita anche i plugin con una command source, inclusi i plugin forzatamente abilitati nelle impostazioni gestite enabledPlugins, a meno che disableCommandPluginSources non sia esplicitamente impostato su false. Le command sources richiedono Claude Code v2.1.229 o successivo
  • Claude Code blocca anche i comandi headersHelper del marketplace a meno che disableCommandPluginSources non sia esplicitamente impostato su false, tranne per un marketplace che le impostazioni gestite stesse dichiarano

Vedi cosa viene eseguito sotto allowManagedHooksOnly.

Le voci degli hook si uniscono tra i livelli di impostazioni piuttosto che sostituirsi a vicenda: le impostazioni utente, progetto e locale aggiungono i loro hook senza rimuovere quelli gestiti, e l'impostazione disableAllHooks non può disabilitare gli hook gestiti da fuori le impostazioni gestite.

Le allowlist degli hook HTTP si applicano agli hook da ogni fonte, incluse le impostazioni di policy gestite:

  • allowedHttpHookUrls: quando definito a qualsiasi livello di impostazioni, Claude Code esegue un handler hook HTTP solo se il suo URL corrisponde all'allowlist unito
  • httpHookAllowedEnvVars: quando definito, Claude Code interpola solo le variabili di ambiente in quella lista negli header degli hook

Modelli matcher

Il campo matcher filtra quando gli hook si attivano. Come viene valutato un matcher dipende dai caratteri che contiene:

Valore matcher Valutato come Esempio
"*", "", o omesso Corrisponde a tutto si attiva ad ogni occorrenza dell'evento
Solo lettere, cifre, _, -, spazi, ,, e | Stringa esatta, o lista di stringhe esatte separate da | o , con spazi bianchi opzionali circostanti Bash corrisponde solo allo strumento Bash; Edit|Write e Edit, Write corrispondono ciascuno a uno dei due strumenti esattamente; code-reviewer corrisponde solo a quel tipo di agente
Contiene qualsiasi altro carattere Espressione regolare JavaScript, non ancorata ^Notebook corrisponde a qualsiasi strumento il cui nome inizia con Notebook; mcp__memory__.* corrisponde a ogni strumento dal server memory

Un matcher sul percorso dell'espressione regolare viene testato con RegExp.prototype.test di JavaScript, che ha successo su una corrispondenza in qualsiasi punto del valore. Edit.* corrisponde sia a Edit che a NotebookEdit; racchiudi il pattern in ^ e $, come in ^Edit$, quando hai bisogno di una corrispondenza di intera stringa.

FileChanged e StopFailure utilizzano un set di corrispondenza esatta più ristretto di sole lettere, cifre, _, e |. Un trattino, spazio, o virgola in un matcher per questi due eventi lo mantiene sul percorso dell'espressione regolare, e solo | separa le alternative. Ogni altro evento con supporto matcher nella tabella che segue accetta | o ,.

L'evento FileChanged non segue queste regole quando costruisce la sua lista di osservazione. Vedi FileChanged.

Ogni tipo di evento corrisponde su un campo diverso:

Evento Cosa filtra il matcher Valori matcher di esempio
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied nome dello strumento Bash, Edit|Write, mcp__.*
SessionStart come è iniziata la sessione startup, resume, clear, compact, fork
Setup quale flag CLI ha attivato il setup init, maintenance
SessionEnd perché è terminata la sessione clear, resume, logout, prompt_input_exit, other
Notification tipo di notifica permission_prompt, idle_prompt, auth_success, elicitation_dialog, elicitation_url_dialog, elicitation_complete, elicitation_response, agent_needs_input, agent_completed, quota_auto_resume_fired, quota_auto_resume_stale, quota_auto_resume_disabled
SubagentStart tipo di agente general-purpose, Explore, Plan, nomi di agenti personalizzati, o nomi con scope plugin come ^my-plugin:reviewer$
PreCompact, PostCompact cosa ha attivato la compattazione manual, auto
PreModelSwitch, PostModelSwitch nome canonico del modello a cui la sessione passa, come descritto sotto PreModelSwitch claude-opus-5, claude-opus-4-6|claude-opus-5, .*opus.*
SubagentStop tipo di agente stessi valori di SubagentStart
ConfigChange fonte di configurazione user_settings, project_settings, local_settings, policy_settings, skills
CwdChanged nessun supporto matcher si attiva sempre ad ogni occorrenza
DirectoryAdded come è stata aggiunta la directory slash_command, register_repo_root
FileChanged nomi di file letterali da osservare (vedi FileChanged) .envrc|.env
StopFailure tipo di errore rate_limit, overloaded, authentication_failed, oauth_org_not_allowed, account_on_hold, billing_error, invalid_request, model_not_found, server_error, max_output_tokens, cloud_credential_error, unknown
InstructionsLoaded motivo del caricamento session_start, nested_traversal, path_glob_match, include, compact
UserPromptExpansion nome del comando i tuoi nomi di skill o comando
Elicitation nome del server MCP i tuoi nomi di server MCP configurati
ElicitationResult nome del server MCP stessi valori di Elicitation
UserPromptSubmit, PostToolBatch, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove, MessageDisplay nessun supporto matcher si attiva sempre ad ogni occorrenza

La corrispondenza di StopFailure su cloud_credential_error richiede Claude Code v2.1.267 o successivo, la prima versione che segnala i fallimenti di caricamento delle credenziali sotto quel valore piuttosto che server_error o unknown.

Per la maggior parte degli eventi, Claude Code valuta il matcher rispetto a un campo dall'input JSON che invia al tuo hook su stdin. Per gli eventi dello strumento, quel campo è tool_name. Per PreModelSwitch e PostModelSwitch, Claude Code valuta il matcher rispetto al nome canonico che deriva da to_model, come descritto sotto PreModelSwitch. Ogni sezione hook event elenca l'insieme completo dei valori matcher e lo schema di input per quell'evento.

Questo esempio esegue uno script di linting solo quando Claude scrive o modifica un file:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/lint-check.sh"
          }
        ]
      }
    ]
  }
}

Se aggiungi un campo matcher a un evento senza supporto matcher, viene silenziosamente ignorato.

Per gli eventi dello strumento, puoi filtrare più strettamente impostando il campo if sui singoli handler hook. if utilizza la sintassi delle regole di permesso per corrispondere al nome dello strumento e agli argomenti insieme, quindi "Bash(git *)" viene eseguito quando qualsiasi sottocomando dell'input Bash corrisponde a git * e "Edit(*.ts)" viene eseguito solo per i file TypeScript.

Corrispondere ai tool MCP

I tool del server MCP appaiono come tool regolari negli eventi dello strumento (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied), quindi puoi farli corrispondere allo stesso modo di qualsiasi altro nome di strumento.

I tool MCP seguono il modello di denominazione mcp__<server>__<tool>, ad esempio:

  • mcp__memory__create_entities: tool create entities del server Memory
  • mcp__filesystem__read_file: tool read file del server Filesystem
  • mcp__github__search_repositories: tool search del server GitHub

Per corrispondere a ogni tool da un server, aggiungi .* al prefisso del server. .* è obbligatorio: un matcher come mcp__memory o mcp__brave-search contiene solo caratteri di corrispondenza esatta, quindi viene confrontato come una stringa esatta e non corrisponde a nessun tool.

  • mcp__memory__.* corrisponde a tutti i tool dal server memory
  • mcp__brave-search__.* corrisponde a tutti i tool da un server il cui nome contiene un trattino
  • mcp__.*__write.* corrisponde a qualsiasi tool il cui nome inizia con write da qualsiasi server

I tool da un server MCP fornito da plugin utilizzano un segmento di server con scope che include il nome del plugin: mcp__plugin_<plugin-name>_<server-name>__<tool>. Un matcher scritto rispetto alla chiave del server nudo non si attiva mai per questi tool. Per un plugin denominato my-plugin che raggruppa un server sotto la chiave db, un tool query appare come mcp__plugin_my-plugin_db__query, quindi il matcher per ogni tool da quel server è mcp__plugin_my-plugin_db__.*. Utilizza lo stesso nome di tool con scope nel campo if di un handler. Vedi Plugin-provided MCP servers per come viene costruito il nome con scope.

Questo esempio registra tutte le operazioni del server memory e convalida le operazioni di scrittura da qualsiasi server MCP:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__memory__.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
          }
        ]
      },
      {
        "matcher": "mcp__.*__write.*",
        "hooks": [
          {
            "type": "command",
            "command": "/home/user/scripts/validate-mcp-write.py"
          }
        ]
      }
    ]
  }
}

Campi handler hook

Ogni oggetto nell'array hooks interno è un handler hook: il comando shell, endpoint HTTP, tool MCP, prompt LLM, o agente che viene eseguito quando il matcher corrisponde. Ci sono cinque tipi:

  • Command hooks (type: "command"): esegui un comando shell. Il tuo script riceve l'input JSON dell'evento su stdin e comunica i risultati indietro attraverso codici di uscita e stdout.
  • HTTP hooks (type: "http"): invia l'input JSON dell'evento come richiesta HTTP POST a un URL. L'endpoint comunica i risultati indietro attraverso il corpo della risposta utilizzando lo stesso formato di output JSON degli hook di comando.
  • MCP tool hooks (type: "mcp_tool"): chiama un tool su un server MCP configurato. L'output di testo del tool viene trattato come stdout di hook di comando.
  • Prompt hooks (type: "prompt"): invia un prompt a un modello Claude per la valutazione a turno singolo. Il modello restituisce la sua decisione come JSON. Vedi Prompt-based hooks.
  • Agent hooks (type: "agent"): genera un subagent che può utilizzare tool come Read, Grep, e Glob per verificare le condizioni prima di restituire una decisione. Gli agent hook sono sperimentali e potrebbero cambiare. Vedi Agent-based hooks.

Tutti gli hook corrispondenti vengono eseguiti in parallelo. Se definisci lo stesso handler in più di un file di impostazioni, viene eseguito una volta. Una copia dello stesso handler di un plugin o skill rimane separata.

Gli handler vengono eseguiti nella directory corrente con l'ambiente di Claude Code. Se la directory corrente non esiste più, ad esempio un worktree o una directory temporanea che un'altra shell ha eliminato a metà sessione, Claude Code esegue gli hook di comando dal primo di questi che esiste ancora: la directory in cui è iniziata la sessione, la radice del progetto, la tua home directory, o la directory temporanea del sistema. Claude Code registra un avviso che nomina la directory di fallback nel debug log.

La variabile di ambiente $CLAUDE_CODE_REMOTE è "true" negli ambienti web remoti e non è impostata nella CLI locale. Claude Code v2.1.199 e successivo imposta $CLAUDE_CODE_BRIDGE_SESSION_ID all'ID della sessione Remote Control mentre la sessione locale ha una connessione Remote Control attiva.

Campi comuni

Questi campi si applicano a tutti i tipi di hook:

Campo Obbligatorio Descrizione
type sì "command", "http", "mcp_tool", "prompt", o "agent"
if no Sintassi delle regole di permesso per filtrare quando questo hook viene eseguito, come "Bash(git *)" o "Edit(*.ts)". Il comando hook viene eseguito solo se la chiamata allo strumento corrisponde al pattern. Vedi la tabella di corrispondenza Bash per come i pattern Bash vengono valutati rispetto ai sottocomandi, $(), e backtick. Valutato solo su eventi dello strumento: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, e PermissionDenied. Su altri eventi, un hook con if impostato non viene mai eseguito
timeout no Secondi prima di annullare. Claude Code non lo applica su un hook di comando che esegui con async: true. Impostazioni predefinite: 600 per command, http, e mcp_tool; 30 per prompt; 60 per agent. Claude Code abbassa l'impostazione predefinita di command, http, e mcp_tool a 30 su UserPromptSubmit, PreModelSwitch, e PostModelSwitch, e a 10 su MessageDisplay. Gli hook SessionEnd condividono un budget di 1,5 secondi; se le tue impostazioni impostano un timeout per hook più lungo, Claude Code aumenta il budget per corrispondere, fino a 60 secondi
statusMessage no Messaggio spinner personalizzato visualizzato mentre l'hook viene eseguito
once no Se true, Claude Code rimuove l'hook dopo la sua prima esecuzione riuscita. Un'esecuzione che fallisce, blocca con codice di uscita 2, o scade lascia l'hook in posizione, quindi viene eseguito di nuovo al prossimo evento corrispondente. Onorato solo per gli hook dichiarati nel frontmatter dello skill; ignorato nei file di impostazioni e nel frontmatter dell'agente

Il campo if contiene esattamente una regola di permesso. Non c'è sintassi &&, ||, o lista per combinare le regole; per applicare più condizioni, definisci un handler hook separato per ciascuna.

In una condizione if per uno strumento di file, un pattern di directory a segmento singolo come "Edit(src/**)" corrisponde solo alla directory src nella directory di lavoro e ai file sotto di essa. Per corrispondere a una directory denominata src a qualsiasi profondità, scrivi "Edit(**/src/**)". Prima di v2.1.214, "Edit(src/**)" corrispondeva a una directory denominata src a qualsiasi profondità sotto la directory di lavoro.

Come i pattern `if` corrispondono ai comandi Bash

Per i pattern Bash nel campo if, se il tuo comando hook viene eseguito dipende dalla forma del pattern e dal comando Bash che Claude sta invocando. Gli assegnamenti VAR=value iniziali vengono rimossi prima della corrispondenza.

Pattern if Comando Bash L'hook viene eseguito? Perché
Bash(git *) FOO=bar git push sì gli assegnamenti iniziali vengono rimossi; git push corrisponde
Bash(git *) npm test && git push sì ogni sottocomando viene controllato; git push corrisponde
Bash(rm *) echo $(rm -rf /) sì i comandi dentro $() e backtick vengono controllati; rm -rf / corrisponde
Bash(rm *) echo $(date) no nessun sottocomando corrisponde a rm *
Bash(git push *) echo $(date) sì i pattern che specificano più del nome del comando eseguono comunque l'hook su $(), backtick, o $VAR

Quando Claude Code non può determinare quali comandi esegue l'input Bash, esegue il tuo hook indipendentemente dal pattern. Poiché il filtro if è best-effort, utilizza il sistema di permessi piuttosto che un hook per applicare un allow o deny rigido.

Campi command hook

Oltre ai campi comuni, gli hook di comando accettano questi campi:

Campo Obbligatorio Descrizione
command sì Comando shell da eseguire. Con args, l'eseguibile da generare direttamente. Vedi Exec form e shell form
args no Lista di argomenti. Quando presente, command viene risolto come un eseguibile e generato direttamente con args come vettore di argomenti, senza shell coinvolto. Vedi Exec form e shell form
async no Se true, viene eseguito in background senza bloccare. Vedi Run hooks in the background
asyncRewake no Se true, viene eseguito in background e riattiva Claude al codice di uscita 2. Lo stderr dell'hook, o stdout se stderr è vuoto, viene mostrato a Claude come un promemoria di sistema in modo che possa reagire a un fallimento di background di lunga durata
shell no Shell da utilizzare per questo hook. Accetta "bash" o "powershell". Impostazione predefinita "bash", o "powershell" su Windows quando Git Bash non è installato. L'impostazione di "powershell" esegue il comando tramite PowerShell su Windows. Non richiede CLAUDE_CODE_USE_POWERSHELL_TOOL poiché gli hook generano PowerShell direttamente. Ignorato quando args è impostato
onFailure no Cosa succede all'azione quando l'hook fallisce: "continue", il valore predefinito, o "block". Vedi Blocca l'azione quando un hook fallisce. Richiede Claude Code v2.1.295 o successivo
Exec form e shell form

Un hook di comando viene eseguito come exec form quando args è impostato, e shell form quando args è omesso. Imposta args ogni volta che l'hook fa riferimento a un placeholder di percorso, poiché ogni elemento viene passato come un argomento senza virgolette. Ometti args quando hai bisogno di funzioni shell come pipe o &&, o quando nessuno dei due problemi si applica.

Exec form viene eseguito quando args è presente. Claude Code risolve command come un eseguibile su PATH e lo genera direttamente con args come vettore di argomenti. Non c'è shell, quindi ogni elemento args è un argomento esattamente come scritto, e i placeholder di percorso come ${CLAUDE_PLUGIN_ROOT} vengono sostituiti in command e in ogni elemento args come stringhe semplici. I caratteri speciali come apostrofi, $, e backtick passano attraverso verbatim perché non c'è shell per interpretarli. Non avviene tokenizzazione shell su nessuna piattaforma.

Shell form viene eseguito quando args è assente. La stringa command viene passata a una shell: sh -c su macOS e Linux, Git Bash su Windows, o PowerShell quando Git Bash non è installato. Imposta il campo shell per scegliere esplicitamente. La shell tokenizza la stringa, espande le variabili, e interpreta pipe, &&, reindirizzamenti, e glob.

Questo esempio esegue uno script Node raggruppato con un plugin. Exec form passa il percorso dello script risolto come un argomento senza virgolette:

{
  "type": "command",
  "command": "node",
  "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/format.js", "--fix"]
}

La shell form equivalente ha bisogno di virgolette per gestire i percorsi con spazi o caratteri speciali:

{
  "type": "command",
  "command": "node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/format.js --fix"
}

Entrambe le forme supportano gli stessi placeholder di percorso, ed entrambe li esportano come variabili di ambiente CLAUDE_PROJECT_DIR, CLAUDE_PLUGIN_ROOT, e CLAUDE_PLUGIN_DATA sul processo generato, quindi uno script può leggere process.env.CLAUDE_PLUGIN_ROOT indipendentemente da come è stato lanciato.

Gli hook plugin inoltre sostituiscono i valori ${user_config.*}, solo in exec form: il valore viene sostituito in command e in ogni elemento args come una stringa semplice, quindi nessuna shell lo ri-analizza.

Un hook plugin in shell form il cui command fa riferimento a ${user_config.*} fallisce con un errore invece di essere eseguito. Per utilizzare un valore di opzione da un hook in shell form, leggi la variabile di ambiente $CLAUDE_PLUGIN_OPTION_<KEY>, come $CLAUDE_PLUGIN_OPTION_WEBHOOK_URL per un'opzione webhook_url, o imposta args per passare l'hook a exec form. Prima di v2.1.207, i comandi degli hook plugin in shell form sostituivano anche ${user_config.*}.

Campi HTTP hook

Oltre ai campi comuni, gli hook HTTP accettano questi campi:

Campo Obbligatorio Descrizione
url sì URL a cui inviare la richiesta POST
headers no Header HTTP aggiuntivi come coppie chiave-valore. I valori supportano l'interpolazione delle variabili di ambiente utilizzando la sintassi $VAR_NAME o ${VAR_NAME}. Solo le variabili elencate in allowedEnvVars vengono risolte
allowedEnvVars no Lista di nomi di variabili di ambiente che possono essere interpolate nei valori degli header. I riferimenti alle variabili non elencate vengono sostituiti con stringhe vuote. Obbligatorio affinché avvenga qualsiasi interpolazione di variabili di ambiente
onFailure no Cosa succede all'azione quando l'hook fallisce: "continue", il valore predefinito, o "block". Vedi Blocca l'azione quando un hook fallisce. Richiede Claude Code v2.1.295 o successivo

Claude Code invia l'input JSON dell'hook come corpo della richiesta POST con Content-Type: application/json. Il corpo della risposta utilizza lo stesso formato di output JSON degli hook di comando.

La gestione degli errori differisce dagli hook di comando; vedi HTTP response handling.

Questo esempio invia gli eventi PreToolUse a un servizio di convalida locale, autenticandosi con un token dalla variabile di ambiente MY_TOKEN:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "http",
            "url": "http://localhost:8080/hooks/pre-tool-use",
            "timeout": 30,
            "headers": {
              "Authorization": "Bearer $MY_TOKEN"
            },
            "allowedEnvVars": ["MY_TOKEN"]
          }
        ]
      }
    ]
  }
}

Campi MCP tool hook

Oltre ai campi comuni, gli hook MCP tool accettano questi campi:

Campo Obbligatorio Descrizione
server sì Nome di un server MCP configurato. Per un server fornito da plugin, questo è il nome con scope plugin:<plugin-name>:<server-name>, come plugin:my-plugin:db, non la chiave del server nudo
tool sì Nome del tool da chiamare su quel server
input no Argomenti passati al tool. I valori stringa supportano la sostituzione ${path} dall'input JSON dell'hook, come "${tool_input.file_path}"

Questo esempio chiama il tool security_scan sul server MCP my_server dopo ogni Write o Edit, passando il percorso del file modificato:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "mcp_tool",
            "server": "my_server",
            "tool": "security_scan",
            "input": { "file_path": "${tool_input.file_path}" }
          }
        ]
      }
    ]
  }
}
Come viene letto il risultato del tool

Claude Code legge il contenuto di testo del tool allo stesso modo in cui legge stdout di hook di comando, seguendo la regola di analisi sotto il codice di uscita 0. Se il tool restituisce isError: true, l'hook produce un errore non bloccante e l'esecuzione continua.

Quando il server è ancora in connessione

Su eventi dove un hook può bloccare o cambiare il risultato, come PreToolUse o Stop, Claude Code attende un server in connessione prima di chiamare il tool, per al massimo MCP_TIMEOUT e entro il timeout dell'hook stesso. Su eventi osservazionali, come Notification o SessionEnd, non attende.

Un server che mostra lo stato cached si connette quando l'hook chiama il suo tool. Se il server non è connesso a quel punto, l'hook produce un errore non bloccante e l'esecuzione continua. L'hook non avvia mai un flusso OAuth, quindi autentica il server da /mcp prima.

Eventi che si attivano prima che i server MCP siano disponibili

SessionStart al lancio, incluso con --continue o --resume, e ogni evento Setup si attivano prima che i server MCP della sessione siano disponibili agli hook. Claude Code salta i loro hook mcp_tool senza chiamare il tool, e il debug log registra mcp_tool hooks are not available for the 'SessionStart' hook event (no MCP client context), o lo stesso messaggio che nomina Setup. Quando SessionStart si attiva di nuovo più tardi nella sessione, dopo /clear o una compattazione, i suoi hook mcp_tool vengono eseguiti. Per qualsiasi cosa la sessione abbia bisogno al lancio, utilizza un hook type: "command" su SessionStart invece.

Campi prompt e agent hook

Oltre ai campi comuni, gli hook prompt e agent accettano questi campi:

Campo Obbligatorio Descrizione
prompt sì Testo del prompt da inviare al modello. Utilizza $ARGUMENTS come placeholder per l'input JSON dell'hook. Sfuggi con una barra rovesciata per includere testo letterale: \$1.00 viene renderizzato come $1.00
model no Modello da utilizzare per la valutazione. Impostazione predefinita al modello che Claude Code utilizza per la funzionalità di background

Riferisci gli script per percorso

Utilizza questi placeholder per fare riferimento agli script degli hook relativi alla radice del progetto o del plugin, indipendentemente dalla directory di lavoro quando l'hook viene eseguito:

  • ${CLAUDE_PROJECT_DIR}: la radice del progetto dove è iniziata la sessione. Claude Code imposta anche questa variabile nell'ambiente dei server MCP stdio e dei server LSP dei plugin.
  • ${CLAUDE_PLUGIN_ROOT}: la directory di installazione del plugin, per gli script raggruppati con un plugin. Vedi variabili di ambiente del plugin per come il percorso si comporta tra gli aggiornamenti.
  • ${CLAUDE_PLUGIN_DATA}: la directory di dati persistenti del plugin, per le dipendenze e lo stato che dovrebbero sopravvivere agli aggiornamenti del plugin.

Preferisci exec form per qualsiasi hook che faccia riferimento a un placeholder di percorso. In shell form, racchiudi ogni placeholder tra virgolette doppie.

Questo esempio utilizza ${CLAUDE_PROJECT_DIR} per eseguire un verificatore di stile dalla directory .claude/hooks/ del progetto dopo qualsiasi chiamata dello strumento Write o Edit:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-style.sh",
"args": []
}
]
}
]
}
}

Hook in skill e agenti

Oltre ai file di impostazioni e ai plugin, gli hook possono essere definiti direttamente negli skill e nei subagenti utilizzando il frontmatter, nello stesso formato di configurazione degli hook basati su impostazioni. Per quanto tempo Claude Code li mantiene registrati dipende dal componente:

  • Hook del subagent: Claude Code li esegue solo mentre quel subagent è in esecuzione e li rimuove quando finisce. Claude Code converte un hook Stop qui in SubagentStop, l'evento che si attiva quando un subagent si completa.
  • Hook dello skill: Claude Code li registra quando tu o Claude invocate lo skill e continua a eseguirli per il resto della sessione, su turni dopo il turno dello skill stesso. Per fare in modo che Claude Code rimuova un hook dopo la sua prima esecuzione riuscita, imposta once: true su di esso.

Questo skill definisce un hook PreToolUse che esegue uno script di convalida della sicurezza prima di ogni comando Bash:

---
name: secure-operations
description: Perform operations with security checks
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/security-check.sh"
---

I subagenti utilizzano lo stesso formato nel loro frontmatter YAML.

Gli hook del frontmatter in uno skill del progetto seguono la stessa regola di fiducia dell'area di lavoro degli hook nei file di impostazioni. Claude Code li registra quando tu o Claude invocate lo skill, incluso in un'esecuzione -p in una cartella che non hai ancora fidata.

Gli hook del frontmatter in un subagent del progetto vengono eseguiti solo dopo che accetti la finestra di dialogo di fiducia dell'area di lavoro per la cartella da cui proviene il file dell'agente. Una sessione -p non conta come accettazione. Cosa viene eseguito prima di fidarti di una cartella confronta questo con la regola del file di impostazioni, e la pagina dei subagenti elenca quali ambiti sono esenti. Prima di v2.1.218, questi hook potevano essere eseguiti da cartelle che non avevi fidata.

Il menu `/hooks`

Digita /hooks in Claude Code per aprire un browser di sola lettura per i tuoi hook configurati. L'elenco etichetta ogni hook con la sua provenienza, come le impostazioni utente, le impostazioni di progetto, le impostazioni locali, un plugin o la sessione corrente.

Seleziona un hook per vedere il testo completo di ciò che esegue e dove è definito, come il percorso del suo file di impostazioni o il nome del suo plugin.

Per sfogliare tutti gli eventi hook, compresi quelli senza hook configurati, seleziona All events alla fine dell'elenco.

Disabilita o rimuovi gli hook

Per rimuovere un hook definito in un file di impostazioni, elimina la sua voce da quel file.

Per disabilitare temporaneamente tutti gli hook senza rimuoverli, imposta "disableAllHooks": true nel tuo file di impostazioni. Claude Code legge il valore rimasto dopo che la precedenza delle impostazioni si applica, quindi un "disableAllHooks": false nel .claude/settings.json di un progetto sostituisce un true nelle tue impostazioni utente. Per disattivare gli hook per un'esecuzione qualunque siano le impostazioni del progetto, passa --settings '{"disableAllHooks": true}', che ha la precedenza sulle impostazioni di progetto e locale. Non c'è modo di disabilitare un singolo hook mantenendolo nella configurazione.

L'impostazione disableAllHooks rispetta la gerarchia delle impostazioni gestite. Se un amministratore ha configurato gli hook attraverso le impostazioni di policy gestite, disableAllHooks impostato nelle impostazioni utente, progetto, o locale non può disabilitare quegli hook gestiti. Solo disableAllHooks impostato a livello di impostazioni gestite può disabilitare gli hook gestiti. Per la portata completa di ogni livello, vedi disableAllHooks.

Le modifiche dirette agli hook nei file di impostazioni vengono normalmente rilevate automaticamente dal file watcher.

Input e output dell'hook

I command hook ricevono dati JSON tramite stdin e comunicano i risultati attraverso codici di uscita, stdout e stderr. Gli HTTP hook ricevono lo stesso JSON come corpo della richiesta POST e comunicano i risultati attraverso il corpo della risposta HTTP. Questa sezione copre i campi e il comportamento comuni a tutti gli eventi. Ogni sezione dell'evento sotto Hook events include il suo schema di input specifico e le opzioni di controllo della decisione.

Su macOS e Linux, i command hook vengono eseguiti nella loro propria sessione senza un terminale di controllo. Il processo hook e qualsiasi processo figlio non possono aprire /dev/tty o inviare sequenze di escape direttamente all'interfaccia Claude Code. Windows non ha /dev/tty.

Per mostrare un messaggio all'utente su qualsiasi piattaforma, restituisci systemMessage nell'output JSON. Alcuni eventi lo scartano o lo consegnano altrove, e ogni sezione dell'evento lo specifica. Per attivare una notifica desktop, impostare un titolo della finestra o suonare il campanello, restituisci invece terminalSequence.

Campi di input comuni

Gli hook event ricevono questi campi come JSON, oltre ai campi specifici dell'evento documentati in ogni sezione hook event. Per i command hook, questo JSON arriva tramite stdin. Per gli HTTP hook, arriva come corpo della richiesta POST.

Campo Descrizione
session_id Identificatore della sessione corrente
prompt_id UUID che identifica il prompt dell'utente attualmente in elaborazione. Corrisponde all'attributo prompt.id sugli eventi OpenTelemetry, quindi puoi correlare l'output dell'hook con la telemetria per un singolo prompt. Assente fino al primo input dell'utente
transcript_path Percorso al JSON della conversazione. Il file della trascrizione viene scritto in modo asincrono e potrebbe rimanere indietro rispetto alla conversazione in memoria, quindi potrebbe non includere ancora i messaggi più recenti del turno corrente quando un hook si attiva. Gli hook che necessitano del testo finale dell'assistente del turno corrente dovrebbero usare last_assistant_message su Stop e SubagentStop invece di leggere la trascrizione
cwd Directory di lavoro corrente quando l'hook viene invocato
scratchpad_dir Percorso alla directory scratchpad della sessione, dove Claude mantiene i file di lavoro temporanei. Assente quando la sessione non ha uno scratchpad o la directory temporanea non è disponibile. Richiede Claude Code v2.1.257 o successivo
permission_mode Modalità di permesso corrente: "default", "plan", "acceptEdits", "auto", "dontAsk" o "bypassPermissions". La modalità etichettata Manual arriva come "default", mai come "manual", quindi gli script che corrispondono a "default" continuano a funzionare. Non tutti gli eventi ricevono questo campo. Controlla l'esempio JSON in ogni sezione hook event
effort Oggetto con un campo level che contiene il livello di sforzo in vigore quando l'hook viene eseguito: "low", "medium", "high", "xhigh" o "max". Se imposti un livello che il modello attivo non supporta, level segnala il livello che Claude Code ha effettivamente eseguito; Adjust effort level spiega come lo sceglie. L'oggetto corrisponde al campo effort della riga di stato. Presente per gli eventi che si attivano all'interno di un contesto di utilizzo degli strumenti, come PreToolUse, PostToolUse, Stop e SubagentStop, quando il modello corrente supporta il parametro effort. Il livello è disponibile anche ai comandi hook e allo strumento Bash come variabile d'ambiente $CLAUDE_EFFORT.
hook_event_name Nome dell'evento che si è attivato

Quando si esegue con --agent o all'interno di un subagent, vengono inclusi due campi aggiuntivi:

Campo Descrizione
agent_id Identificatore univoco per il subagent. Presente solo quando l'hook si attiva all'interno di una chiamata di subagent. Usalo per distinguere le chiamate dell'hook del subagent dalle chiamate del thread principale.
agent_type Nome dell'agente (ad esempio, "Explore" o "security-reviewer"). Presente quando la sessione usa --agent o l'hook si attiva all'interno di un subagent. Per i subagent, il tipo del subagent ha la precedenza sul valore --agent della sessione. Consulta SubagentStart per i valori che i subagent personalizzati e dei plugin segnalano e per come scrivere un matcher rispetto a un nome con ambito plugin.

Solo gli hook SessionStart possono ricevere un campo model, e Claude Code non lo include sempre. Gli hook PreModelSwitch e PostModelSwitch ricevono invece from_model e to_model, quindi usa un hook PostModelSwitch per seguire il modello mentre cambia durante una sessione.

Non esiste una variabile d'ambiente $CLAUDE_MODEL. L'hook può leggere $ANTHROPIC_MODEL se lo imposti nella tua shell, ma quel valore non cambia quando cambi modello con /model durante una sessione.

Un processo hook eredita l'ambiente padre, a parte le variabili dell'esportatore OTEL_* che Claude Code rimuove da ogni sottoprocesso che genera e, quando CLAUDE_CODE_SUBPROCESS_ENV_SCRUB è impostato su 1, le variabili che rimuove. In una sessione che riceve la configurazione HIPAA, Claude Code rimuove anche le credenziali Anthropic dall'ambiente dell'hook.

Ad esempio, un hook PreToolUse per un comando Bash riceve questo su stdin:

{
  "session_id": "abc123",
  "prompt_id": "550e8400-e29b-41d4-a716-446655440000",
  "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
  "cwd": "/home/user/my-project",
  "scratchpad_dir": "/tmp/claude-1000/-home-user-my-project/abc123/scratchpad",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test",
    "description": "Run test suite",
    "timeout": 120000,
    "run_in_background": false
  },
  "tool_use_id": "toolu_01ABC123..."
}

I campi tool_name, tool_input e tool_use_id sono specifici dell'evento. Ogni sezione hook event documenta i campi aggiuntivi per quell'evento.

Output del codice di uscita

Il codice di uscita del tuo hook dice a Claude Code se continuare con l'azione che ha attivato l'hook, come una chiamata a uno strumento o un prompt. Un'esecuzione che termina ha uno di tre risultati:

  • Successo: il tuo hook esce con 0. Claude Code applica tutti i campi di output JSON che il tuo hook ha stampato, e l'azione procede a meno che quei campi non la blocchino o la neghino.
  • Errore bloccante: il tuo hook esce con 2. Sugli eventi che possono bloccare, Claude Code interrompe l'azione.
  • Errore non bloccante: il tuo hook esce con qualsiasi altro codice, oppure fallisce in qualche altro modo, ad esempio non avviandosi o stampando JSON non valido. L'azione procede, e su eventi come PreToolUse vedi un avviso <hook name> hook error nella trascrizione. Se vuoi che un hook fallito blocchi l'azione, imposta onFailure: "block".

Ciò che il tuo hook stampa su stdout può cambiare il risultato. Ad esempio, se un hook PreToolUse esce con 1 ma stampa JSON che supera la convalida, l'esecuzione è un successo e sono i campi JSON a decidere cosa accade. Per trovare il risultato del tuo hook su un evento come PreToolUse, fai corrispondere ciò che ha stampato su stdout nella prima colonna con il suo codice di uscita nella riga in alto:

Stdout Exit 0 Exit 2 Qualsiasi altro codice di uscita
Oggetto JSON che supera la convalida dello schema Successo. I campi si applicano Errore bloccante. Claude Code legge comunque i campi, ma non possono sovrascrivere il blocco Successo. Claude Code ignora il codice di uscita, e decidono solo i campi. Con onFailure: "block", questo conta come un fallimento
JSON che non può essere analizzato o non supera la convalida dello schema Errore non bloccante. L'avviso riporta il messaggio di analisi o di convalida Errore bloccante. Il tuo stderr è il motivo Errore non bloccante. L'avviso riporta il messaggio di analisi o di convalida
Testo semplice, o niente Successo Errore bloccante. Il tuo stderr è il motivo Errore non bloccante. L'avviso riporta la prima riga del tuo stderr

Alcuni eventi hanno regole proprie:

  • WorktreeCreate: qualsiasi codice di uscita diverso da zero fa fallire la creazione del worktree, indipendentemente da ciò che dice il tuo JSON.
  • WorktreeRemove: qualsiasi codice di uscita diverso da zero fa fallire la rimozione del worktree se la directory esiste ancora dopo.
  • Stop, SubagentStop, TaskCompleted e l'hook UserPromptSubmit di un plugin: quando il tuo hook esce con 2 senza nulla su stdout e il suo stderr dice che manca un file, ad esempio No such file or directory, Claude Code tratta l'esecuzione come un errore non bloccante.
  • Elicitation e ElicitationResult: Claude Code applica il tuo hookSpecificOutput quando il tuo hook esce con 0, e lo ignora su qualsiasi altro codice di uscita.
  • Eventi che scartano l'output dell'hook, come StopFailure: Claude Code ignora il tuo JSON su qualsiasi codice di uscita, a parte i campi con effetti collaterali come terminalSequence, che si attivano comunque.

Per verificare cosa fa il codice di uscita 2 sul tuo evento, consulta Exit code 2 behavior per event. Per verificare quali campi di decisione rispetta, consulta Decision control.

Exit code 0

Exit 0 significa successo, ed è il codice di uscita previsto quando stampi JSON per il controllo strutturato.

Per la maggior parte degli eventi, Claude Code scrive stdout nel log di debug e non lo mostra nella trascrizione. Le eccezioni sono UserPromptSubmit, UserPromptExpansion, SessionStart e PostModelSwitch, dove Claude Code aggiunge lo stdout in testo semplice come contesto che Claude può vedere e su cui può agire.

Se Claude Code legge il tuo stdout come JSON output o come testo semplice dipende da come inizia e finisce, ignorando gli spazi bianchi circostanti:

  • Inizia con { e finisce con }: Claude Code lo analizza come JSON. Quando l'output è composto da due o più righe che si analizzano ciascuna come JSON da sole, e nessuna riga è un oggetto JSON output che imposta un campo, Claude Code tratta l'intero output come testo semplice. Quando una di quelle righe imposta un campo, l'intero output è un errore di analisi.
  • Inizia con { ma non finisce con }: Claude Code lo tratta come testo semplice.
  • Inizia con qualsiasi altra cosa: Claude Code lo tratta come testo semplice, anche quando è un array JSON o una stringa JSON tra virgolette.

Quando Claude Code tenta di analizzare il tuo stdout come JSON e non ci riesce, oppure l'oggetto analizzato non supera la convalida dello schema, l'esecuzione è un errore non bloccante. L'avviso <hook name> hook error riporta il messaggio di analisi o di convalida. Sugli eventi che aggiungono lo stdout in testo semplice come contesto, Claude Code non aggiunge lo stdout che non è riuscito ad analizzare.

Claude non vede mai lo stderr di un hook che esce con 0. Per leggerlo tu stesso su eventi come PreToolUse, abilita il debug logging. Per mostrare un avviso a Claude da un hook PostToolUse o PostToolUseFailure, esci invece con 2 in modo che Claude veda lo stderr anche se lo strumento è già stato eseguito.

Exit code 2

Esci con il codice 2 per bloccare l'azione. Sugli eventi che possono bloccare, Claude Code interrompe l'azione: un hook PreToolUse blocca la chiamata allo strumento, ad esempio, e un hook UserPromptSubmit rifiuta il prompt.

Il messaggio che accompagna il blocco è lo stderr del tuo hook. Se il tuo hook ha stampato anche JSON che prende una decisione di blocco, Claude Code usa invece il motivo di quella decisione.

Exit 2 blocca anche quando il tuo hook stampa JSON:

  • JSON che supera la convalida dello schema: Claude Code legge comunque i campi di JSON output, ma non possono sovrascrivere il blocco. Nemmeno un permissionDecision con valore "allow" lascia passare l'azione. Su Elicitation e ElicitationResult, l'hookSpecificOutput di un hook che esce con 2 viene ignorato.
  • JSON che non supera la convalida dello schema: l'hook blocca comunque. Claude Code usa il tuo stderr come motivo di blocco e registra l'errore di convalida nel log di debug.

Questo script blocca i comandi rm uscendo con 2 e lascia ogni altro comando al normale flusso dei permessi:

#!/bin/bash
# Legge l'input JSON da stdin, controlla il comando
input=$(cat)
command=$(jq -r '.tool_input.command' <<<"$input")

if [[ "$command" == rm* ]]; then
  echo "Blocked: rm commands are not allowed" >&2
  exit 2  # Errore bloccante: la chiamata allo strumento viene impedita
fi

exit 0  # Nessuna decisione: si applica il normale flusso dei permessi

Con questo script registrato come hook PreToolUse su Bash, un comando che inizia con rm viene bloccato, e Claude riceve lo stderr dell'hook come errore dello strumento, preceduto dal nome dell'evento, dal nome dello strumento e dal comando dell'hook:

PreToolUse:Bash hook error: [${CLAUDE_PROJECT_DIR}/.claude/hooks/no-rm.sh]: Blocked: rm commands are not allowed

Altri codici di uscita

Quando il tuo hook esce con un codice diverso da 0 o 2 e stampa testo semplice o niente su stdout, l'esecuzione è un errore non bloccante. Vedi un avviso <hook name> hook error nella trascrizione con Failed with non-blocking status code: e la prima riga dello stderr del tuo hook. Ad esempio, quando un hook PreToolUse su Bash stampa something broke su stderr ed esce con 1, l'avviso PreToolUse:Bash hook error riporta questa riga:

Failed with non-blocking status code: something broke

Per acquisire lo stderr completo anziché la sua prima riga, abilita il debug logging.

Anche un hook che non riesce ad avviarsi è un errore non bloccante. In forma shell, quando il percorso dello script non esiste o non è eseguibile, la shell esce con un codice come 127 e l'avviso riporta il messaggio dell'interprete, ad esempio Failed with non-blocking status code: /bin/sh: /path/to/hook.sh: No such file or directory. Quando configuri un hook di policy, controlla se compare questo avviso alla sua prima esecuzione, perché un percorso digitato male in settings.json significa che l'hook non viene mai eseguito. Per bloccare invece l'azione, imposta onFailure: "block".

Timeout

A parte un command hook che esegui con async: true, Claude Code annulla un hook command, http o mcp_tool che raggiunge il suo timeout, scartando l'output dell'hook, quindi sulla maggior parte degli eventi un hook scaduto non produce alcuna decisione.

Su PreModelSwitch, un hook annullato al suo timeout blocca il cambio di modello. Su PreToolUse, le due famiglie di hook si comportano diversamente:

Bloccare l'azione quando un hook fallisce

Sulla maggior parte degli eventi, quando un hook fallisce o va in timeout, Claude Code esegue comunque l'azione, quindi un hook di policy con un percorso sbagliato o uno script che si arresta in modo anomalo lascia passare tutto. Per bloccare invece l'azione, imposta "onFailure": "block" su un hook command o http. Il valore predefinito è "continue". Richiede Claude Code v2.1.295 o successivo.

Questo hook PreToolUse in .claude/settings.json esegue uno script di progetto prima di ogni comando Bash, e blocca il comando se lo script fallisce:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node",
            "args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/check-command.js"],
            "onFailure": "block"
          }
        ]
      }
    ]
  }
}

Per provarlo, lascia mancante check-command.js e chiedi a Claude di eseguire un comando Bash come ls. Claude Code blocca la chiamata, e l'errore include failed; blocking because onFailure is "block" seguito dall'output di errore di node, qui ridotto a una riga:

PreToolUse:Bash hook error: [node ${CLAUDE_PROJECT_DIR}/.claude/hooks/check-command.js]: failed; blocking because onFailure is "block"
Error: Cannot find module '/path/to/project/.claude/hooks/check-command.js'

Dopo un timeout, il messaggio dice timed out invece di failed. Senza onFailure impostato, lo stesso script mancante è un errore non bloccante e ls viene eseguito.

Ciascuno dei seguenti casi conta come un fallimento:

  • Impossibile avviarsi: un command hook non riesce ad avviarsi, ad esempio perché lo script o l'eseguibile non esiste
  • Codice di uscita diverso da 0 o 2: conta per un command hook anche se ha stampato JSON che consente l'azione, come permissionDecision: "allow". Per restituire una decisione JSON, esci con 0
  • Errore HTTP: la connessione di un HTTP hook fallisce, oppure lo stato della risposta non è 2xx
  • Timeout: l'hook raggiunge il suo timeout
  • Output non valido: l'output JSON non può essere analizzato o non supera la convalida dello schema. Per un HTTP hook, conta anche un corpo 2xx che non è né vuoto né un oggetto JSON. Lo stdout in testo semplice di un command hook non è un fallimento

Con "block" impostato, un fallimento fa ciò che fa il codice di uscita 2 su quell'evento, tranne su PermissionRequest, dove nega la richiesta. Ad esempio, un fallimento di PreToolUse blocca la chiamata allo strumento e un fallimento di UserPromptSubmit blocca il prompt.

Il campo non ha effetto su questi hook:

  • Hook Stop, SubagentStop, TaskCompleted e TeammateIdle: il codice di uscita 2 su questi eventi rimanda Claude a continuare a lavorare, e Claude non può riparare un hook che non viene eseguito
  • Command hook in background: i command hook che impostano async o asyncRewake

Exit code 2 behavior per event

Il codice di uscita 2 è il modo in cui un hook segnala "fermati, non farlo". L'effetto dipende dall'evento, perché alcuni eventi rappresentano azioni che possono essere bloccate (come una chiamata a uno strumento che non è ancora avvenuta) e altri rappresentano cose che sono già accadute o non possono essere impedite.

Hook event Può bloccare? Cosa accade con exit 2
PreToolUse Sì Blocca la chiamata allo strumento
PermissionRequest No Il codice di uscita 2 non viene rispettato per questo evento e il flusso dei permessi procede invariato. Nega invece tramite l'oggetto decision
UserPromptSubmit Sì Blocca il prompt, quindi non raggiunge mai Claude. Consulta What a blocked prompt leaves behind
UserPromptExpansion Sì Blocca l'espansione
Stop Sì Impedisce a Claude di fermarsi, continua la conversazione
SubagentStop Sì Impedisce al subagent di fermarsi
TeammateIdle Sì Impedisce al compagno di squadra di andare inattivo, quindi continua a lavorare
TaskCreated Sì Annulla la creazione dell'attività
TaskCompleted Sì Impedisce che l'attività sia contrassegnata come completata
ConfigChange Sì Impedisce che la modifica della configurazione abbia effetto (tranne policy_settings)
StopFailure No L'output e il codice di uscita vengono ignorati, tranne terminalSequence
PostToolUse No Mostra stderr a Claude; lo strumento è già stato eseguito
PostToolUseFailure No Mostra stderr a Claude; lo strumento è già fallito
PostToolBatch Sì Interrompe il ciclo agentico prima della successiva chiamata al modello
PermissionDenied No Il codice di uscita e stderr vengono ignorati perché il rifiuto è già avvenuto. Usa il JSON hookSpecificOutput.retry: true per dire al modello che può riprovare; Claude Code ignora retry: true per i no-verdict denials
Notification No Il codice di uscita e stderr vengono ignorati
SubagentStart No Mostra stderr solo all'utente
SessionStart No Mostra stderr solo all'utente
Setup No Il codice di uscita e stderr vengono ignorati
SessionEnd No Mostra stderr solo all'utente
CwdChanged No Mostra stderr solo all'utente
DirectoryAdded No Stderr va nel log di debug; la directory è già stata aggiunta
FileChanged No Mostra stderr solo all'utente
PreCompact Sì Blocca la compattazione
PostCompact No Mostra stderr solo all'utente
PreModelSwitch Sì Blocca il cambio di modello e mostra stderr all'utente
PostModelSwitch No Mostra stderr solo all'utente; il modello è già cambiato
Elicitation Sì Rifiuta la richiesta, e non compare alcuna finestra di dialogo
ElicitationResult Sì Blocca la risposta (l'azione diventa decline)
WorktreeCreate Sì Qualsiasi codice di uscita diverso da zero fa fallire la creazione del worktree
WorktreeRemove Sì Qualsiasi codice di uscita diverso da zero fa fallire la rimozione del worktree se la directory esiste ancora dopo. Consulta WorktreeRemove per cosa accade alla directory
InstructionsLoaded No Il codice di uscita viene ignorato
MessageDisplay No Il testo originale viene visualizzato

Per SessionStart, SubagentStart e PostModelSwitch, Claude Code mostra lo stderr del codice di uscita 2 nella trascrizione come un avviso <hook name> hook error, nello stesso modo in cui mostra un errore non bloccante. Claude non lo vede, e la sessione o il subagent procede. Per SubagentStart, l'avviso appare nella trascrizione del subagent stesso, non nella conversazione padre.

Gestione della risposta HTTP

Gli HTTP hook usano i codici di stato HTTP e i corpi della risposta invece dei codici di uscita e di stdout. I risultati seguenti si applicano alla maggior parte degli eventi; un evento con un proprio contratto di fallimento nella tabella per evento, come WorktreeCreate, applica quel contratto anche a un HTTP hook fallito:

  • 2xx con corpo vuoto: successo, equivalente al codice di uscita 0 senza output
  • 2xx con corpo costituito da un oggetto JSON: analizzato usando lo stesso schema JSON output dei command hook. Un corpo che non supera la convalida dello schema è un errore non bloccante
  • 2xx con qualsiasi altro corpo, come testo semplice: errore non bloccante, gestito nello stesso modo di uno stato non-2xx. Claude Code non aggiunge il testo al contesto di Claude
  • Stato non-2xx: errore non bloccante, l'esecuzione continua
  • Errore di connessione: errore non bloccante, l'esecuzione continua
  • Timeout: l'hook viene annullato, come descritto in Timeouts

Gli HTTP hook non possono segnalare un errore bloccante solo attraverso il codice di stato: uno stato non-2xx o una connessione fallita è un errore non bloccante. Per bloccare una chiamata a uno strumento o negare un permesso, restituisci una risposta 2xx con un corpo JSON contenente i campi di decisione appropriati. Per bloccare l'azione quando la richiesta fallisce o restituisce uno stato non-2xx, imposta onFailure: "block".

Output JSON

I codici di uscita ti permettono solo di bloccare o restare in silenzio, ma l'output JSON ti dà un controllo più granulare. Invece di uscire con il codice 2 per bloccare, esci con 0 e stampa un oggetto JSON su stdout. Claude Code legge campi specifici da quel JSON per controllare il comportamento, incluso il decision control per bloccare, consentire o inoltrare la decisione all'utente.

Lo stdout del tuo hook deve contenere solo l'oggetto JSON. Se il profilo della tua shell stampa testo all'avvio, può interferire con l'analisi del JSON. Consulta Hook JSON has no effect nella guida alla risoluzione dei problemi.

Le stringhe additionalContext, systemMessage e initialUserMessage di un hook, e il suo stdout semplice, sono limitate a 10.000 caratteri:

  • Ambito: Claude Code misura ogni stringa singolarmente, anche quando più hook vengono eseguiti per lo stesso evento. Per l'output JSON, ogni campo viene misurato separatamente; lo stdout semplice viene misurato nel complesso.
  • Oltre il limite: Claude Code salva l'output in un file nella directory della sessione e lo sostituisce con il percorso del file e un'anteprima fino ai primi 2.000 caratteri. Un risultato Bash valido di grandi dimensioni viene gestito nello stesso modo, descritto in Output limits. A differenza di quel limite Bash, questo limite non ha un'impostazione o una variabile d'ambiente per aumentarlo.
  • Lettura del file: Claude Code non chiede a Claude di leggere il file, quindi mantieni entro il limite tutto ciò che Claude deve sempre vedere.

L'oggetto JSON supporta tre tipi di campi:

  • I campi universali come continue sono elencati nella tabella seguente. Ogni evento li accetta, ma alcuni eventi li scartano o consegnano systemMessage in un punto diverso dalla trascrizione. Ogni sezione dell'evento lo specifica. terminalSequence funziona anche su quegli eventi, con le eccezioni elencate in Emit terminal notifications.
  • decision e reason di livello superiore sono usati da alcuni eventi per bloccare o fornire feedback.
  • hookSpecificOutput è un oggetto annidato per gli eventi che necessitano di un controllo più ricco. Richiede un campo hookEventName impostato sul nome dell'evento.
Campo Predefinito Descrizione
continue true Se false, Claude interrompe completamente l'elaborazione dopo l'esecuzione dell'hook. Ha la precedenza su qualsiasi campo di decisione specifico dell'evento
stopReason nessuno Messaggio mostrato all'utente quando continue è false. Rimane nella conversazione, quindi Claude lo vede se la conversazione continua
suppressOutput false Non ha effetto: Claude Code accetta il campo ma non agisce su di esso. Lo stdout di un hook riuscito non viene mai mostrato nella trascrizione e viene registrato nel log di debug
systemMessage nessuno Messaggio di avviso mostrato all'utente. Nell'output dell'Agent SDK e di --output-format stream-json, può arrivare come SDKInformationalMessage
terminalSequence nessuno Una sequenza di escape del terminale che Claude Code emette per tuo conto, come una notifica desktop, un titolo della finestra o un campanello. Limitato a OSC 0/1/2/9/99/777 e BEL. Se il valore contiene qualcosa al di fuori dell'allowlist, il campo viene ignorato. Usalo invece di scrivere su /dev/tty, che non è disponibile per gli hook

Per fermare Claude completamente:

{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }

Per gli hook PreToolUse e PostToolUse, l'arresto si applica anche quando la chiamata allo strumento fallisce o si completa mentre Claude sta ancora trasmettendo in streaming una risposta.

Emettere notifiche del terminale

Gli hook vengono eseguiti senza un terminale di controllo, quindi scrivere sequenze di escape direttamente su /dev/tty non funziona. Restituisci invece la sequenza di escape nel campo terminalSequence e Claude Code la emette per te attraverso il proprio percorso di scrittura sul terminale. Questo è privo di race condition, funziona all'interno di tmux e GNU screen, e funziona su Windows dove non esiste /dev/tty.

Il campo accetta una stringa di una o più sequenze di escape presenti nell'allowlist:

  • OSC 0, 1, 2: titoli della finestra e dell'icona
  • OSC 9: notifiche di iTerm2, ConEmu, Windows Terminal e WezTerm, incluso l'avanzamento nella barra delle applicazioni 9;4
  • OSC 99: notifiche di Kitty
  • OSC 777: notifiche di urxvt, Ghostty e Warp
  • BEL semplice

Le sequenze possono essere terminate con BEL o con ST. Qualsiasi cosa al di fuori dell'allowlist, incluse le sequenze CSI per cursore e colore, le sequenze OSC della tavolozza, i collegamenti ipertestuali OSC 8, le scritture negli appunti OSC 52 e OSC 1337, viene rifiutata e il campo viene ignorato.

Claude Code scrive la sequenza autonomamente quando elabora l'output del tuo hook, quindi il campo funziona sugli eventi che scartano systemMessage e continue, come Notification e StopFailure. Ha due limiti:

  • Claude Code scrive la sequenza solo in una sessione interattiva, e solo mentre la sua interfaccia è sullo schermo. In modalità non interattiva con il flag -p e nell'Agent SDK, ignora il campo.
  • Un command hook WorktreeCreate non può restituire JSON, perché Claude Code legge il suo stdout come percorso del worktree. Un HTTP hook WorktreeCreate restituisce JSON e può includere il campo.

L'esempio seguente attiva una notifica desktop da un hook Notification. La sequenza di escape viene costruita con escape ottali di printf in modo che i byte di controllo non compaiano mai sulla riga di comando della shell, e jq -n --arg costruisce l'output JSON in modo che virgolette, barre rovesciate e a capo nel messaggio di notifica siano correttamente sottoposti a escape:

#!/bin/bash
# Hook di notifica: avvisa il desktop quando Claude Code ha bisogno di attenzione.
input=$(cat)
title="Claude Code"
body=$(jq -r '.message // "Needs your attention"' <<<"$input")
seq=$(printf '\033]777;notify;%s;%s\007' "$title" "$body")
jq -nc --arg seq "$seq" '{terminalSequence: $seq}'

La forma { "terminalSequence": "..." } è la stessa da qualsiasi shell o linguaggio.

Aggiungere contesto per Claude

Il campo additionalContext passa una stringa dal tuo hook nella finestra di contesto di Claude. Claude Code avvolge la stringa in un promemoria di sistema e la inserisce nella conversazione nel punto in cui l'hook si è attivato. Claude legge il promemoria alla successiva richiesta al modello, ma non appare come messaggio di chat nell'interfaccia.

Restituisci additionalContext all'interno di hookSpecificOutput insieme al nome dell'evento:

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "This file is generated. Edit src/schema.ts and run `bun generate` instead."
  }
}

Il punto in cui appare il promemoria dipende dall'evento:

Quando più hook restituiscono additionalContext per lo stesso evento, Claude riceve tutti i valori.

Se un valore supera i 10.000 caratteri, Claude Code scrive invece il testo in un file nella directory della sessione e passa a Claude il percorso del file con un'anteprima fino ai primi 2.000 caratteri. Claude può leggere il file, ma Claude Code non glielo chiede.

Usa additionalContext per informazioni che Claude dovrebbe conoscere sullo stato corrente del tuo ambiente o sull'operazione appena eseguita:

  • Stato dell'ambiente: il branch corrente, la destinazione del deploy o i feature flag attivi
  • Regole di progetto condizionali: quale comando di test si applica al file appena modificato, quali directory sono di sola lettura in questo worktree
  • Dati esterni: issue aperte assegnate a te, risultati CI recenti, contenuto recuperato da un servizio interno

Per le istruzioni che non cambiano mai, preferisci CLAUDE.md. Si carica senza eseguire uno script ed è il luogo standard per le convenzioni di progetto statiche.

Scrivi il testo come affermazioni fattuali anziché come istruzioni di sistema imperative. Frasi come "La destinazione del deploy è la produzione" o "Questo repository usa bun test" si leggono come informazioni di progetto. Il testo formulato come comandi di sistema fuori banda può attivare le difese di Claude contro la prompt injection, il che porta Claude a segnalarti il testo invece di trattarlo come contesto.

Claude Code salva il testo iniettato nella trascrizione della sessione. Per gli eventi a metà sessione come PostToolUse o UserPromptSubmit, quando riprendi con --continue o --resume, Claude Code riproduce il testo salvato anziché rieseguire l'hook per i turni passati, quindi valori come timestamp o SHA di commit diventano obsoleti. Gli hook SessionStart vengono eseguiti di nuovo alla ripresa con source impostato su "resume", o su "fork" se hai aggiunto --fork-session, quindi possono aggiornare il loro contesto.

Controllo della decisione

Non tutti gli eventi supportano il blocco o il controllo del comportamento tramite JSON. Gli eventi che lo fanno usano ciascuno un insieme diverso di campi per esprimere quella decisione. Usa questa tabella come riferimento rapido prima di scrivere un hook:

Eventi Modello di decisione Campi chiave
UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact decision di livello superiore decision: "block", reason. Stop e SubagentStop accettano anche hookSpecificOutput.additionalContext per feedback non di errore che continua la conversazione
TeammateIdle, TaskCompleted Codice di uscita o continue: false Il codice di uscita 2 blocca l'azione con feedback stderr. Il JSON {"continue": false, "stopReason": "..."} interrompe anche completamente il compagno di squadra, come il comportamento dell'hook Stop; TaskCompleted lo ignora quando è lo strumento TaskUpdate ad attivare l'evento
TaskCreated Codice di uscita o decision di livello superiore Il codice di uscita 2 o decision: "block" annulla l'attività e restituisce il messaggio a Claude. continue: false viene ignorato
PreToolUse hookSpecificOutput permissionDecision (allow/deny/ask/defer), permissionDecisionReason
PreModelSwitch hookSpecificOutput o decision di livello superiore permissionDecision (allow/deny/ask), permissionDecisionReason. Anche decision: "block" annulla il cambio
PermissionRequest hookSpecificOutput decision.behavior (allow/deny)
PermissionDenied hookSpecificOutput retry: true dice al modello che può riprovare la chiamata allo strumento negata; Claude Code lo ignora per i no-verdict denials
WorktreeCreate restituzione del percorso Il command hook stampa il percorso su stdout; l'HTTP hook restituisce hookSpecificOutput.worktreePath. Il fallimento dell'hook o un percorso mancante fa fallire la creazione
WorktreeRemove Codice di uscita Qualsiasi codice di uscita diverso da zero fa fallire la rimozione se la directory esiste ancora dopo. L'output JSON viene scartato
Elicitation, ElicitationResult hookSpecificOutput o decision di livello superiore action (accept/decline/cancel), content (valori dei campi del modulo). Anche decision: "block" rifiuta
MessageDisplay hookSpecificOutput displayContent sostituisce il testo visualizzato sullo schermo. Solo visualizzazione: la trascrizione e ciò che Claude vede mantengono l'originale
SessionStart, SubagentStart, PostModelSwitch Solo contesto hookSpecificOutput.additionalContext aggiunge contesto per Claude. SessionStart accetta anche initialUserMessage, watchPaths, sessionTitle e reloadSkills. Nessun blocco o controllo della decisione
Setup, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, DirectoryAdded, FileChanged Nessuno Nessun controllo della decisione. Usati per effetti collaterali come il logging o la pulizia

Alcuni eventi possono anche riscrivere il contenuto anziché limitarsi a consentirlo o bloccarlo:

  • PreToolUse: updatedInput direttamente sotto hookSpecificOutput sostituisce gli argomenti di uno strumento prima che venga eseguito. Consulta PreToolUse decision control
  • PermissionRequest: updatedInput all'interno dell'oggetto decision. Consulta PermissionRequest decision control
  • PostToolUse: updatedToolOutput sostituisce il risultato dello strumento. Consulta PostToolUse decision control
  • UserPromptSubmit: non può sostituire il prompt; si limita a iniettare additionalContext insieme a esso

Per i casi d'uso di oscuramento o trasformazione, intercetta in PreToolUse gli input degli strumenti in uscita e in PostToolUse i risultati degli strumenti in entrata.

Ecco esempi di ogni modello in azione:

L'unico valore per decision è "block". Per consentire all'azione di procedere, ometti decision dal JSON, oppure esci con 0 senza alcun JSON:

{
"decision": "block",
"reason": "Test suite must pass before proceeding"
}

Per esempi estesi, tra cui la convalida dei comandi Bash, il filtraggio dei prompt e gli script di approvazione automatica, consulta What you can automate nella guida e l'implementazione di riferimento del validatore di comandi Bash.

Eventi hook

Ogni evento corrisponde a un punto del ciclo di vita di Claude Code in cui gli hook possono essere eseguiti. Le sezioni seguenti sono ordinate secondo il ciclo di vita: dalla configurazione della sessione, attraverso il ciclo agentico, fino alla fine della sessione. Ogni sezione descrive quando l'evento viene attivato, quali matcher supporta, l'input JSON che riceve e come controllare il comportamento tramite l'output.

SessionStart

Viene eseguito quando Claude Code avvia una nuova sessione o riprende una sessione esistente. È utile per caricare il contesto di sviluppo, come le issue esistenti o le modifiche recenti al tuo codebase, o per impostare le variabili d'ambiente. Per un contesto statico che non richiede uno script, usa invece CLAUDE.md.

SessionStart viene eseguito in ogni sessione, quindi mantieni questi hook veloci. Sono supportati solo gli hook type: "command" e type: "mcp_tool". Consulta Campi degli hook per strumenti MCP per sapere quando vengono eseguiti gli hook mcp_tool.

Il valore del matcher corrisponde al modo in cui è stata avviata la sessione:

Matcher Quando viene attivato
startup Nuova sessione
resume --resume, --continue o /resume
clear /clear
compact Compattazione automatica o manuale
fork Una nuova sessione derivata da una esistente: --fork-session con --resume o --continue, la copia in background di /fork, /branch o una conversazione che sposti in background

Prima della v2.1.214, le sessioni derivate riportavano come origine "resume".

Quando avvii una sessione interattiva, riprendi una conversazione all'avvio con --continue o --resume, oppure esegui /clear, gli hook SessionStart vengono eseguiti in background. Puoi digitare subito, e una conversazione ripresa appare senza attendere gli hook. La prima risposta di Claude attende comunque il completamento degli hook, così il loro contesto raggiunge Claude.

Quando cambi conversazione con /resume all'interno di una sessione, il cambio attende invece il completamento degli hook. Se esegui /clear o passi a un'altra conversazione mentre gli hook in background sono ancora in esecuzione, nulla di ciò che restituiscono viene applicato alla sessione.

La stessa attesa si applica all'avvio, anche per una sessione ripresa: un prompt che invii mentre gli hook SessionStart sono ancora in esecuzione non raggiunge Claude finché non terminano.

Durante entrambe le attese, premi Esc per riportare il prompt nell'input senza inviarlo. Gli hook continuano a essere eseguiti.

Input di SessionStart

Oltre ai campi di input comuni, gli hook SessionStart ricevono source e, facoltativamente, model, agent_type e session_title:

Campo Descrizione
source Come è stata avviata la sessione: "startup" per le nuove sessioni, "resume" per le sessioni riprese, "clear" dopo /clear, "compact" dopo la compattazione o "fork" per una nuova sessione derivata da una esistente
model L'identificatore del modello attivo. Può essere omesso, ad esempio dopo /clear o quando una sessione viene ripristinata tramite il recupero della conversazione, quindi verifica la presenza del campo prima di leggerlo
agent_type Il nome dell'agente, presente quando avvii Claude Code con claude --agent <name>
session_title Il titolo personalizzato della sessione, presente quando ne è impostato uno, ad esempio con --name, /rename, l'output sessionTitle di un hook o renameSession() dell'Agent SDK. Un hook che emette sessionTitle può controllare prima questo campo per evitare di sovrascrivere un titolo personalizzato esistente

Una sessione a cui non hai dato un nome può comunque avere un titolo generato. Quel titolo non è un titolo personalizzato e non appare in session_title.

Quando source è "resume" o "fork" e la trascrizione contiene almeno una risposta di Claude, gli hook SessionStart ricevono anche i quattro campi seguenti. Il tuo hook può usarli per riportare quanto costa riprendere una conversazione inattiva prima della prima richiesta, ad esempio in un systemMessage. Questi campi richiedono Claude Code v2.1.251 o successiva.

Campo Descrizione
seconds_since_last_response Secondi di tempo reale trascorsi dall'ultima risposta nella trascrizione ripresa
context_tokens Token che la prima richiesta della sessione ripresa invia di nuovo come prompt
prompt_cache_likely_expired true quando l'ultima risposta è più vecchia della durata della cache del prompt della sessione o una compattazione successiva ha sostituito la conversazione memorizzata nella cache
estimated_cache_write_usd Costo stimato in dollari USA per scrivere context_tokens nella cache del prompt sul modello della sessione, esclusa la risposta

Questo esempio mostra l'input per una sessione ripresa 90 minuti dopo la sua ultima risposta:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SessionStart",
  "source": "resume",
  "model": "claude-opus-5",
  "seconds_since_last_response": 5400,
  "context_tokens": 182340,
  "prompt_cache_likely_expired": true,
  "estimated_cache_write_usd": 1.1396
}

Controllo delle decisioni di SessionStart

Un hook SessionStart può aggiungere contesto per Claude, fornire il primo messaggio dell'utente, impostare il titolo della sessione, monitorare file e ricaricare le skill. Restituisci il campo corrispondente a ciascuna di queste azioni, oltre ai campi di output JSON disponibili per tutti gli hook:

Campo Descrizione
additionalContext Stringa aggiunta al contesto di Claude all'inizio della conversazione, prima del primo prompt. Consulta Aggiungere contesto per Claude per sapere come viene consegnato il testo e cosa inserirvi
initialUserMessage Stringa usata come primo messaggio dell'utente della sessione, in modalità non interattiva con il flag -p. Diventa il primo turno anche se non passi alcun prompt. Un prompt che passi lo segue come turno successivo
sessionTitle Imposta il titolo della sessione, con lo stesso effetto di /rename. Si applica quando source è "startup", "resume" o "fork"
watchPaths Array di percorsi assoluti da monitorare per gli eventi FileChanged durante questa sessione
reloadSkills Booleano. Quando è true, Claude Code analizza di nuovo le directory delle skill e dei comandi dopo il completamento degli hook SessionStart. Consulta Ricaricare le skill installate da un hook

Questo output aggiunge contesto e assegna un nome alla sessione:

{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "Current branch: feat/auth-refactor\nUncommitted changes: src/auth.ts, src/login.tsx\nActive issue: #4211 Migrate to OAuth2",
    "sessionTitle": "auth-refactor"
  }
}

Un hook che aggiunge solo contesto può stamparlo senza costruire JSON, perché Claude Code aggiunge lo stdout in testo semplice di un hook SessionStart al contesto di Claude.

Se l'hook SessionStart del tuo plugin fornisce initialUserMessage o sessionTitle, installa il plugin prima dell'avvio della sessione. Claude Code ignora entrambi i campi provenienti da un plugin la cui installazione termina dopo che gli hook SessionStart sono stati eseguiti.

Ricaricare le skill installate da un hook

Per rendere disponibili nella stessa sessione le skill installate da un hook SessionStart, restituisci reloadSkills. Il rilevamento delle skill viene normalmente eseguito prima che gli hook SessionStart terminino, quindi senza questo campo i file che un hook scrive in ~/.claude/skills/ o .claude/skills/ possono mancare quando viene eseguito il primo prompt.

Questo esempio sincronizza un repository di skill condiviso e richiede la nuova analisi:

#!/bin/bash

git -C ~/.claude/skills/team-skills pull --quiet 2>/dev/null || \
  git clone --quiet https://git.example.com/your-org/team-skills.git ~/.claude/skills/team-skills

echo '{"hookSpecificOutput": {"hookEventName": "SessionStart", "reloadSkills": true}}'

L'URL del repository è un segnaposto. Sostituiscilo con il tuo repository di skill.

Rendere persistenti le variabili d'ambiente

Gli hook SessionStart hanno accesso alla variabile d'ambiente CLAUDE_ENV_FILE, che fornisce un percorso di file in cui puoi rendere persistenti le variabili d'ambiente per i successivi comandi Bash.

Per impostare singole variabili d'ambiente, scrivi istruzioni export in CLAUDE_ENV_FILE. Usa l'accodamento (>>) per preservare le variabili impostate da altri hook:

#!/bin/bash

if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
  echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
  echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi

exit 0

Per catturare tutte le modifiche all'ambiente dai comandi di configurazione, confronta le variabili esportate prima e dopo:

#!/bin/bash

ENV_BEFORE=$(export -p | sort)

# Run your setup commands that modify the environment
source ~/.nvm/nvm.sh
nvm use 20

if [ -n "$CLAUDE_ENV_FILE" ]; then
  ENV_AFTER=$(export -p | sort)
  comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
fi

exit 0

Setup

Viene attivato solo quando avvii Claude Code con --init-only, oppure con --init o --maintenance in modalità non interattiva con il flag -p. Non viene attivato al normale avvio. Usalo per l'installazione una tantum di dipendenze o per una pulizia pianificata che attivi esplicitamente da CI o da script, separatamente dal normale avvio della sessione. Per l'inizializzazione per sessione, usa invece SessionStart.

Il valore del matcher corrisponde al flag CLI che ha attivato l'hook:

Matcher Quando viene attivato
init claude --init-only o claude -p --init
maintenance claude -p --maintenance

Quando esegui claude --init-only, Claude Code esegue gli hook Setup e gli hook SessionStart con il matcher startup, poi esce senza avviare una conversazione.

Quando avvii o continui una conversazione con -p, devi anche fornire un prompt, come argomento o tramite pipe su stdin. Puoi omettere il prompt quando un hook SessionStart fornisce initialUserMessage o quando riprendi una sessione con una chiamata a uno strumento differita.

In caso di successo, --init-only non stampa nulla nel terminale. Per confermare che gli hook sono stati eseguiti, avvia con claude --debug-file <path> --init-only, sostituendo <path> con il percorso di un file di log, e controlla nel log le voci degli hook Setup e SessionStart.

Poiché Setup non viene attivato a ogni avvio, un plugin che necessita di una dipendenza installata non può basarsi solo su Setup. Lo schema pratico consiste nel verificare la dipendenza al primo utilizzo e installarla se manca, ad esempio con un hook o una skill che verifica la presenza di ${CLAUDE_PLUGIN_DATA}/node_modules ed esegue npm install se assente. Consulta la directory dei dati persistenti per sapere dove archiviare le dipendenze installate. Se distribuisci il tuo plugin tramite un marketplace, potresti non aver bisogno di questo schema: Claude Code installa automaticamente le dipendenze dei pacchetti Node.js idonee quando memorizza il plugin nella cache.

Input di Setup

Oltre ai campi di input comuni, gli hook Setup ricevono un campo trigger impostato su "init" o "maintenance":

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Setup",
  "trigger": "init"
}

Controllo delle decisioni di Setup

Gli hook Setup non possono bloccare; l'esecuzione continua con qualsiasi codice di uscita. Con qualsiasi codice di uscita, Claude Code scarta i campi di output JSON di un hook Setup, come systemMessage, continue e hookSpecificOutput.additionalContext. Con -p, lo stdout, lo stderr e il codice di uscita di un hook Setup appaiono nell'output dell'esecuzione solo come eventi hook_response quando avvii con --output-format stream-json --verbose.

Gli hook Setup hanno accesso a CLAUDE_ENV_FILE. Le variabili scritte in quel file persistono nei successivi comandi Bash della sessione, come negli hook SessionStart. Su Setup vengono eseguiti solo gli hook type: "command". Un hook type: "mcp_tool" su Setup viene sempre saltato, come descritto in Campi degli hook per strumenti MCP.

InstructionsLoaded

Viene attivato quando un file CLAUDE.md o .claude/rules/*.md viene caricato nel contesto. Questo evento viene attivato all'avvio della sessione per i file caricati immediatamente e di nuovo in seguito quando i file vengono caricati in modo differito, ad esempio quando Claude accede a una sottodirectory che contiene un CLAUDE.md annidato o quando le regole condizionali con frontmatter paths: corrispondono. L'hook non supporta il blocco né il controllo delle decisioni. Viene eseguito in modo asincrono per scopi di osservabilità.

Questo evento non viene attivato quando Claude legge direttamente AGENTS.md tramite l'impostazione Project instructions. Viene invece attivato quando un CLAUDE.md importa il tuo AGENTS.md, con load_reason impostato su include come per qualsiasi altro file importato, e quando CLAUDE.md è un collegamento simbolico a esso, come un normale caricamento di CLAUDE.md.

Il matcher viene confrontato con load_reason. Ad esempio, usa "matcher": "session_start" per attivarlo solo per i file caricati all'avvio della sessione, oppure "matcher": "path_glob_match|nested_traversal" per attivarlo solo per i caricamenti differiti.

Input di InstructionsLoaded

Oltre ai campi di input comuni, gli hook InstructionsLoaded ricevono questi campi:

Campo Descrizione
file_path Percorso assoluto del file di istruzioni caricato
memory_type Ambito del file: "User", "Project", "Local" o "Managed"
load_reason Perché il file è stato caricato: "session_start", "nested_traversal", "path_glob_match", "include" o "compact". Il valore "compact" viene attivato quando i file di istruzioni vengono ricaricati dopo un evento di compattazione
globs Pattern glob di percorso dal frontmatter paths: del file, se presenti. Presente solo per i caricamenti path_glob_match
trigger_file_path Percorso del file il cui accesso ha attivato questo caricamento, per i caricamenti differiti
parent_file_path Percorso del file di istruzioni padre che ha incluso questo, per i caricamenti include
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "InstructionsLoaded",
  "file_path": "/Users/my-project/CLAUDE.md",
  "memory_type": "Project",
  "load_reason": "session_start"
}

Controllo delle decisioni di InstructionsLoaded

Gli hook InstructionsLoaded non hanno controllo delle decisioni. Non possono bloccare né modificare il caricamento delle istruzioni. Claude Code scarta i loro campi di output JSON, come systemMessage e continue. Usa questo evento per il logging di audit, il monitoraggio della conformità o l'osservabilità.

UserPromptSubmit

Viene eseguito quando viene inviato un prompt, prima che Claude lo elabori. Ti consente di aggiungere contesto aggiuntivo in base al prompt/alla conversazione, di convalidare i prompt o di bloccare determinati tipi di prompt.

Gli hook UserPromptSubmit non vengono attivati solo sui prompt che digiti. Claude Code li esegue anche quando:

Gli hook UserPromptSubmit hanno un timeout predefinito di 30 secondi per i tipi command, http e mcp_tool, più breve del valore predefinito di 600 secondi per quei tipi nella maggior parte degli altri eventi. Poiché questo hook viene eseguito prima di ogni prompt e blocca l'elaborazione del modello finché non termina, un hook bloccato paralizza la sessione. Se il tuo hook ha bisogno di più tempo, imposta il campo timeout nella voce dell'hook.

Fatta eccezione per un hook di comando che esegui con async: true, un hook UserPromptSubmit di comando, HTTP o strumento MCP che raggiunge il suo timeout viene annullato e il suo output, incluso qualsiasi additionalContext, viene scartato. Il prompt raggiunge comunque Claude senza quel contesto. Per bloccare invece il prompt, imposta onFailure: "block" su un hook di comando o HTTP. La trascrizione mostra un avviso che indica l'hook, il timeout scattato e il fatto che l'output è stato scartato.

Un hook di callback dell'Agent SDK su UserPromptSubmit che raggiunge il timeout blocca il prompt con un messaggio che indica l'hook e il timeout, perché lì un callback può fungere da controllo di policy che non deve fallire in modo permissivo. La sessione continua. Prima della v2.1.208, un timeout di callback su quell'evento terminava il turno con un errore di esecuzione.

Input di UserPromptSubmit

Oltre ai campi di input comuni, gli hook UserPromptSubmit ricevono il campo prompt contenente il testo inviato. Il contenuto incollato che è stato compresso in un segnaposto [Pasted text #N] arriva espanso al suo posto. Nelle sessioni in cui Claude Code contrassegna il testo incollato per Claude, quel contenuto espanso si trova tra una riga <pasted_content id="…"> e una riga </pasted_content id="…">, quindi tieni conto di quelle righe se il tuo hook analizza il prompt.

Gli hook UserPromptSubmit ricevono anche session_title quando la sessione ha un titolo personalizzato, con lo stesso significato del campo session_title di SessionStart.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "UserPromptSubmit",
  "prompt": "Write a function to calculate the factorial of a number"
}

Controllo delle decisioni di UserPromptSubmit

Gli hook UserPromptSubmit possono controllare se un prompt inviato viene elaborato e aggiungere contesto. Tutti i campi di output JSON sono disponibili.

Esistono due modi per aggiungere contesto alla conversazione con codice di uscita 0:

  • Stdout in testo semplice: Claude Code aggiunge al contesto di Claude lo stdout che tratta come testo semplice
  • JSON con additionalContext: usa il formato JSON riportato sotto per un maggiore controllo. Il campo additionalContext viene aggiunto come contesto

Nessuno dei due canali produce una voce visibile nella trascrizione. Lo stdout semplice e il valore di additionalContext vengono inseriti ciascuno come promemoria di sistema che inizia con il nome dell'hook; Claude li legge entrambi. Per confermare la consegna, controlla il log di debug.

Per bloccare un prompt, restituisci un oggetto JSON con decision impostato su "block":

Campo Descrizione
decision "block" ferma il prompt prima che raggiunga Claude. Omettilo per consentire al prompt di procedere
reason Mostrato all'utente quando decision è "block". Non viene aggiunto al contesto
additionalContext Stringa aggiunta al contesto di Claude insieme al prompt inviato. Consulta Aggiungere contesto per Claude
sessionTitle Imposta il titolo della sessione. Usalo per assegnare automaticamente un nome alle sessioni in base al contenuto del prompt
suppressOriginalPrompt Se true quando l'hook blocca il prompt, esclude il testo del prompt dal messaggio di blocco. Consulta Cosa lascia un prompt bloccato

Un hook che blocca uscendo con 2 segue lo stesso percorso di reason: il messaggio di blocco mostra all'utente il testo di stderr, che non viene aggiunto al contesto.

{
  "decision": "block",
  "reason": "Explanation for decision",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "My additional context here",
    "sessionTitle": "My session title",
    "suppressOriginalPrompt": true
  }
}

Cosa lascia un prompt bloccato

Un prompt bloccato non raggiunge mai Claude, ma il suo testo non viene rimosso ovunque. Per impostazione predefinita, il messaggio di blocco mostrato all'utente termina con Original prompt: seguito dal testo inviato, e Claude Code scrive quel messaggio nel file di trascrizione della sessione su disco. Per escludere il testo dal messaggio, stampa un JSON con "suppressOriginalPrompt": true all'interno di hookSpecificOutput. Funziona sia che l'hook blocchi con decision: "block" sia uscendo con 2.

suppressOriginalPrompt modifica solo il messaggio di blocco. Il testo inviato può comunque comparire in file locali come la trascrizione della sessione e la cronologia dei tuoi prompt, quindi un hook di blocco non è un modo per tenere un segreto fuori dal disco. Per limitare o rimuovere quei file, consulta Archiviazione in testo semplice e Cancellare i dati locali.

UserPromptExpansion

Viene eseguito quando un comando digitato dall'utente si espande in un prompt prima di raggiungere Claude. Usalo per impedire l'invocazione diretta di comandi specifici, inserire contesto per una particolare skill o registrare quali comandi invocano gli utenti. Ad esempio, un hook che corrisponde a deploy può bloccare /deploy a meno che non sia presente un file di approvazione, oppure un hook che corrisponde a una skill di revisione può aggiungere la checklist di revisione del team come additionalContext.

Questo evento copre il percorso che PreToolUse non copre: un hook PreToolUse che corrisponde allo strumento Skill viene attivato solo quando Claude chiama lo strumento, ma digitare direttamente /skillname aggira PreToolUse. UserPromptExpansion viene attivato su quel percorso diretto.

Corrisponde su command_name. Lascia vuoto il matcher per attivarlo su ogni comando di tipo prompt.

Input di UserPromptExpansion

Oltre ai campi di input comuni, gli hook UserPromptExpansion ricevono expansion_type, command_name, command_args, command_source e la stringa prompt originale. Il campo expansion_type è slash_command per le skill e i comandi personalizzati, oppure mcp_prompt per i prompt dei server MCP.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../00893aaf.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "UserPromptExpansion",
  "expansion_type": "slash_command",
  "command_name": "example-skill",
  "command_args": "arg1 arg2",
  "command_source": "plugin",
  "prompt": "/example-skill arg1 arg2"
}

Controllo delle decisioni di UserPromptExpansion

Gli hook UserPromptExpansion possono bloccare l'espansione o aggiungere contesto. Tutti i campi di output JSON sono disponibili.

Campo Descrizione
decision "block" impedisce l'espansione del comando. Omettilo per consentirgli di procedere
reason Mostrato all'utente quando decision è "block"
additionalContext Stringa aggiunta al contesto di Claude insieme al prompt espanso. Consulta Aggiungere contesto per Claude

Un hook che blocca uscendo con 2 segue lo stesso percorso di reason: il messaggio di blocco mostra all'utente il testo di stderr.

{
  "decision": "block",
  "reason": "This slash command is not available",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptExpansion",
    "additionalContext": "Additional context for this expansion"
  }
}

MessageDisplay

Viene eseguito mentre un messaggio dell'assistente viene trasmesso sullo schermo. Claude Code mostra il messaggio a incrementi: ogni volta che un gruppo di righe appena completate è pronto per essere visualizzato, l'hook viene eseguito una volta con quelle righe e Claude Code visualizza al loro posto il testo sostitutivo dell'hook. Un messaggio lungo produce diverse chiamate; un messaggio breve può produrne solo una.

Usa MessageDisplay per:

  • rimuovere il markdown per una visualizzazione minimale
  • trasformare il testo che un'applicazione dell'Agent SDK mostra ai suoi utenti
  • oscurare chiavi API o nomi host interni dalle risposte di Claude

Claude Code trattiene ogni gruppo finché il tuo hook non restituisce un risultato, quindi mantieni l'hook veloce. Se l'hook fallisce o va in timeout, Claude Code visualizza il testo originale. Il timeout predefinito per questo evento è di 10 secondi; se il tuo hook ha bisogno di più tempo, imposta il campo timeout nella voce dell'hook.

MessageDisplay riguarda solo la visualizzazione: il testo sostitutivo cambia solo ciò che viene mostrato sullo schermo. La trascrizione e ciò che vede Claude mantengono il testo originale, quindi Claude non vede mai la sostituzione, e la modalità verbose mostra l'originale. L'hook riceve solo il testo dei messaggi dell'assistente, quindi i risultati degli strumenti e il testo che digiti vengono visualizzati invariati.

MessageDisplay non supporta i matcher e viene attivato per ogni messaggio dell'assistente che trasmette testo; i messaggi senza testo, come le risposte composte solo da chiamate a strumenti, non lo attivano.

Nelle esecuzioni non interattive, incluse le query dell'Agent SDK e claude -p, MessageDisplay viene eseguito una volta per messaggio dell'assistente anziché una volta per gruppo di righe. L'unica chiamata arriva dopo il completamento del messaggio e contiene il testo completo del messaggio: index è 0, final è true e delta contiene l'intero messaggio. Un hook che raccoglie il testo delta per ogni messaggio riceve lo stesso testo totale in entrambe le modalità.

Input di MessageDisplay

Oltre ai campi di input comuni, gli hook MessageDisplay ricevono gli identificatori del turno e del messaggio, la posizione di questa chiamata all'interno del messaggio e il nuovo testo in delta. I confini dei gruppi dipendono da come viene trasmesso il testo, quindi usa index e final per tracciare l'avanzamento all'interno di un messaggio anziché aspettarti che le righe siano raggruppate in un modo particolare.

Campo Descrizione
turn_id UUID del turno corrente
message_id UUID del messaggio dell'assistente visualizzato. Stabile in tutti i gruppi dello stesso messaggio. Non è l'id msg_… dell'API, quindi non può essere correlato con gli id dei messaggi della trascrizione
index Indice a base zero di questo gruppo all'interno del messaggio
final true sull'ultimo gruppo del messaggio. Ogni messaggio ha esattamente un gruppo finale
delta Le righe appena completate dal gruppo precedente, inclusi i caratteri di nuova riga finali. Sempre righe intere, tranne il gruppo finale, che può terminare a metà riga. Nelle esecuzioni interattive, il delta del gruppo finale è vuoto quando il messaggio termina con una nuova riga, quindi considera final, e non un delta non vuoto, come segnale di fine messaggio. Nelle esecuzioni dell'Agent SDK e di claude -p, l'unica chiamata contiene l'intero messaggio
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "MessageDisplay",
  "turn_id": "0c9e6a2f-7d41-4f4e-9a15-3f4f7c2b8d10",
  "message_id": "5b2a9c8e-1f63-4d8a-b7c4-9e0d2a6f1c3b",
  "index": 0,
  "final": false,
  "delta": "Here is the plan:\n"
}

Output di MessageDisplay

Oltre ai campi di output JSON disponibili per tutti gli hook, gli hook MessageDisplay possono restituire displayContent per sostituire il delta sullo schermo:

Campo Descrizione
displayContent Testo visualizzato al posto del delta. Omettilo per visualizzare l'originale

Gli hook MessageDisplay non hanno controllo delle decisioni. Non possono bloccare il messaggio né modificare ciò che viene archiviato nella trascrizione o inviato a Claude. Claude Code agisce su displayContent dal loro output JSON e scarta systemMessage e continue.

Questo esempio rimuove la formattazione markdown dalle risposte di Claude per una visualizzazione in testo semplice. Lo script legge ogni gruppo da stdin, rimuove da delta i marcatori del grassetto e i backtick del codice inline, e restituisce il risultato come displayContent.

Registra un hook di comando per l'evento nel tuo file di impostazioni:

{
"hooks": {
"MessageDisplay": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/plain-display.sh",
"args": []
}
]
}
]
}
}

Salva questo script in .claude/hooks/plain-display.sh nel tuo progetto e rendilo eseguibile con chmod +x:

#!/bin/bash
jq '{hookSpecificOutput: {hookEventName: "MessageDisplay", displayContent: (.delta | gsub("\\*\\*"; "") | gsub("`"; ""))}}'

I gruppi senza markdown passano invariati. Se lo script fallisce, ad esempio perché jq manca, Claude Code visualizza il testo originale e segnala l'errore solo nell'output di debug, non nella sessione.

PreToolUse

Viene eseguito dopo che Claude ha creato i parametri dello strumento e prima di elaborare la chiamata allo strumento. Corrisponde a qualsiasi nome di strumento tranne EndConversation: strumenti integrati come Bash, PowerShell, Edit, Write, Read, Glob, Grep, Agent, Workflow, WebFetch, WebSearch, AskUserQuestion e ExitPlanMode, e qualsiasi nome di strumento MCP.

Per eseguire un hook quando un file specifico cambia su disco, indipendentemente da chi lo ha scritto, usa FileChanged anziché far corrispondere per nome gli strumenti di modifica dei file. A differenza di PreToolUse, Claude Code esegue gli hook FileChanged dopo la modifica, e questi non hanno controllo delle decisioni, quindi non possono bloccare la scrittura.

Usa il controllo delle decisioni di PreToolUse per consentire, negare, chiedere o differire la chiamata allo strumento.

Un hook di callback dell'Agent SDK su PreToolUse che supera il timeout blocca la chiamata allo strumento, e Claude riceve un risultato di errore che indica il timeout. Una negazione esplicita restituita da un altro hook ha comunque la precedenza.

Input di PreToolUse

Oltre ai campi di input comuni, gli hook PreToolUse ricevono tool_name, tool_input e tool_use_id.

Per uno strumento MCP, l'input contiene anche mcp_server, un oggetto con il name del server e un source che indica da dove proviene la definizione del server. I valori di source includono plugin, sdk e ambiti di configurazione come user e project. McpServerProvenance nel riferimento dell'Agent SDK li elenca tutti e spiega come trattare un valore che non riconosci. Basa le decisioni di fiducia su source anziché su name o sul prefisso del nome dello strumento mcp__<server>__. Il campo mcp_server richiede Claude Code v2.1.274 o successiva.

Per gli strumenti di file Write, Edit e Read, tool_input.file_path è sempre assoluto:

  • Claude Code espande ~ e i percorsi relativi prima dell'esecuzione degli hook, quindi un hook che corrisponde sui percorsi non può essere aggirato tramite ~ o una scrittura relativa dello stesso percorso
  • Su Windows, il percorso arriva con separatori backslash, anche quando il tuo hook viene eseguito in Git Bash dove $PWD appare come /c/project
  • Un confronto scritto con le barre normali, come un controllo su /src/, non corrisponde mai a un percorso con backslash, e la chiamata allo strumento procede come se l'hook non avesse nulla da bloccare
  • Normalizza i separatori prima del confronto: FILE_PATH="${FILE_PATH//\\//}" in Bash, oppure file_path.replace("\\", "/") in Python, poi fai corrispondere un segmento di percorso come /src/ anziché ancorare con ^, poiché il percorso è assoluto

Una chiamata Write su Windows fornisce:

{
  "hook_event_name": "PreToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "C:\\project\\src\\index.ts",
    "content": "..."
  },
  ...
}

I campi di tool_input dipendono dallo strumento:

Bash

Esegue comandi shell.

Campo Tipo Esempio Descrizione
command string "npm test" Il comando shell da eseguire
description string "Run test suite" Descrizione facoltativa di ciò che fa il comando
timeout number 120000 Timeout facoltativo in millisecondi. I valori superiori al massimo vengono ridotti al massimo anziché rifiutati
run_in_background boolean false Se eseguire il comando in background

Quando un comando Bash modifica file in un repository Git, Claude Code può registrare cosa è cambiato. Registra le modifiche in ogni modalità di permesso quando l'impostazione bashEditDiffEnabled attiva la registrazione; la voce di quell'impostazione indica quali file possono impostarla. Altrimenti le registra solo in modalità auto e in modalità bypassPermissions, e solo quando Claude Code indica a Claude di modificare i file tramite Bash. Imposta bashEditDiffEnabled su false per disattivare la registrazione. I comandi in background e i comandi di sola lettura non contengono alcun diff.

Il tuo hook PostToolUse riceve quindi i file modificati in tool_response.bashEditDiff. L'elenco copre ciò che è cambiato nel repository durante l'esecuzione del comando. I file ignorati da Git e i file nei submodule non sono elencati. Richiede Claude Code v2.1.269 o successiva.

changedFiles e files elencano ciò che il comando ha modificato; i campi rimanenti indicano quanto è completo e affidabile quell'elenco.

Campo Tipo Esempio Descrizione
changedFiles array ["/path/to/src/app.ts"] Percorsi assoluti dei file modificati dal comando, al massimo 200. Presente ogni volta che files contiene un diff o moreFiles è superiore a zero
files array [{"filePath": "/path/to/src/app.ts", "hunks": [...]}] Diff di un massimo di 5 file modificati, per la visualizzazione. created o deleted è true per un file che il comando ha aggiunto o rimosso
moreFiles number 2 Numero di file modificati senza diff in files
unavailable boolean true Impostato quando il diff è incompleto o non è stato possibile ottenerlo
skipped boolean true Impostato per un comando Git che sposta l'albero di lavoro, come git checkout o git stash, per cui Claude Code non calcola alcun diff
shared boolean true Impostato quando un'altra chiamata allo strumento Bash, ad esempio quella di un subagent, è stata eseguita nello stesso repository nello stesso momento, per cui alcune modifiche elencate potrebbero appartenere a quel comando
PowerShell

Esegue comandi PowerShell. Consulta lo strumento PowerShell per la disponibilità per piattaforma.

I campi corrispondono a quelli dello strumento Bash, con la stringa del comando in command:

Campo Tipo Esempio Descrizione
command string "Get-ChildItem -Recurse" Il comando PowerShell da eseguire
description string "List files recursively" Descrizione facoltativa di ciò che fa il comando
timeout number 120000 Timeout facoltativo in millisecondi
run_in_background boolean false Se eseguire il comando in background

Usa Bash|PowerShell negli hook che ispezionano i comandi shell, così coprono entrambi gli strumenti:

  • Su Windows, ovunque lo strumento PowerShell sia abilitato, Claude tratta PowerShell come shell principale e instrada i comandi shell attraverso di essa.
  • Su Windows senza Git Bash, lo strumento viene abilitato automaticamente e Claude Code non registra affatto lo strumento Bash.
  • Un hook che corrisponde solo a Bash non viene mai attivato in quel caso.
Write

Crea o sovrascrive un file.

Campo Tipo Esempio Descrizione
file_path string "/path/to/file.txt" Percorso assoluto del file da scrivere
content string "file content" Contenuto da scrivere nel file
Edit

Sostituisce una stringa in un file esistente.

Campo Tipo Esempio Descrizione
file_path string "/path/to/file.txt" Percorso assoluto del file da modificare
old_string string "original text" Testo da trovare e sostituire
new_string string "replacement text" Testo sostitutivo
replace_all boolean false Se sostituire tutte le occorrenze
Read

Legge il contenuto dei file.

Campo Tipo Esempio Descrizione
file_path string "/path/to/file.txt" Percorso assoluto del file da leggere
offset number 10 Numero di riga facoltativo da cui iniziare la lettura
limit number 50 Numero facoltativo di righe da leggere
Glob

Trova i file che corrispondono a un pattern glob.

Campo Tipo Esempio Descrizione
pattern string "**/*.ts" Pattern glob con cui confrontare i file
path string "/path/to/dir" Directory facoltativa in cui cercare. Il valore predefinito è la directory di lavoro corrente
Grep

Cerca nel contenuto dei file con espressioni regolari.

Campo Tipo Esempio Descrizione
pattern string "TODO.*fix" Pattern di espressione regolare da cercare
path string "/path/to/dir" File o directory facoltativi in cui cercare
glob string "*.ts" Pattern glob facoltativo per filtrare i file
output_mode string "content" "content", "files_with_matches" o "count". Il valore predefinito è "files_with_matches"
-i boolean true Ricerca senza distinzione tra maiuscole e minuscole
multiline boolean false Abilita la corrispondenza su più righe
WebFetch

Recupera ed elabora contenuti web.

Campo Tipo Esempio Descrizione
url string "https://example.com/api" URL da cui recuperare il contenuto
prompt string "Extract the API endpoints" Prompt da eseguire sul contenuto recuperato
offset number 100000 Numero facoltativo di caratteri da saltare dall'inizio della pagina. Claude lo imposta per continuare a leggere una pagina lunga. Richiede Claude Code v2.1.290 o successiva
WebSearch

Effettua ricerche sul web.

Campo Tipo Esempio Descrizione
query string "react hooks best practices" Query di ricerca
allowed_domains array ["docs.example.com"] Facoltativo: include solo i risultati da questi domini
blocked_domains array ["spam.example.com"] Facoltativo: esclude i risultati da questi domini
Agent

Avvia un subagent.

Campo Tipo Esempio Descrizione
prompt string "Find all API endpoints" L'attività che l'agente deve svolgere
description string "Find API endpoints" Breve descrizione dell'attività
subagent_type string "Explore" Tipo di agente specializzato da usare
model string "sonnet" Alias di modello facoltativo per sovrascrivere il valore predefinito

Quando una chiamata Agent in primo piano termina, il tuo hook PostToolUse riceve il risultato del subagent e la telemetria dell'esecuzione in tool_response. Leggi questi campi per ispezionare l'esecuzione; per i totali di token e costi tra i subagent, usa i contatori di token e costi filtrati su query_source "subagent", poiché totalTokens e usage coprono solo la richiesta finale:

Campo Tipo Esempio Descrizione
status string "completed" "completed" per i subagent in primo piano, "async_launched" per i subagent in background. Per impostazione predefinita i subagent vengono eseguiti in background, quindi anche una chiamata Agent che omette run_in_background produce "async_launched"
agentId string "a4d2c8f1e0b3a297" Identificatore dell'esecuzione del subagent
content array [{"type": "text", "text": "Found 12 endpoints..."}] I blocchi di testo finali del subagent oppure, per un subagent il cui report passa attraverso SubagentHandback, una breve nota su quella consegna al loro posto
resolvedModel string "claude-sonnet-4-5" Modello con cui il subagent è partito, che può differire dal modello richiesto
modelsUsed array ["claude-sonnet-4-5", "claude-haiku-4-5"] Modelli usati in ordine, con le ripetizioni consecutive compresse; impostato solo quando il modello è stato cambiato durante l'esecuzione. Richiede Claude Code v2.1.212 o successiva
totalTokens number 12450 Numero di token dalla richiesta API finale del subagent: token di input, output e cache combinati. Non è un totale sull'intera esecuzione
totalDurationMs number 48211 Durata in tempo reale dell'esecuzione del subagent
totalToolUseCount number 7 Numero di chiamate a strumenti effettuate dal subagent
usage object {"input_tokens": 8320, ...} Suddivisione per tipo dei token della richiesta API finale: input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens

Su Claude Code v2.1.271 o successiva, un subagent che viene eseguito con lo strumento SubagentHandback, che Claude Code fornisce in modalità auto, consegna il suo report tramite quello strumento anziché restituirlo come testo. Il campo content del suo risultato completed contiene allora una breve nota su quella consegna anziché il report stesso. Per leggere il report, fai corrispondere un hook PreToolUse o PostToolUse su SubagentHandback e leggi tool_input.message.

Per i subagent in background, lo strumento restituisce un risultato quando l'attività passa in background, quindi tool_response non contiene campi di utilizzo: un avvio in background restituisce immediatamente, e un'attività in primo piano che Claude Code sposta in background durante l'esecuzione restituisce in corrispondenza di quella transizione. Contiene status: "async_launched", agentId, description, prompt, outputFile e resolvedModel.

In una risposta completed, resolvedModel indica il modello con cui il subagent è partito, che può differire dal valore model in tool_input, ad esempio quando si applica availableModels o un altro override. In una risposta async_launched, resolvedModel indica il modello in uso quando l'agente è passato in background, quindi un cambio avvenuto prima del passaggio in background viene riflesso lì. modelsUsed e il comportamento di resolvedModel al momento del passaggio in background richiedono Claude Code v2.1.212 o successiva.

AskUserQuestion

Pone all'utente da una a quattro domande a scelta multipla.

Campo Tipo Esempio Descrizione
questions array [{"question": "Which framework?", "header": "Framework", "options": [{"label": "React", "description": "Component library"}, {"label": "Vue", "description": "Progressive framework"}], "multiSelect": false}] Domande da presentare, ciascuna con una stringa question, un breve header, un array options e un flag multiSelect facoltativo
answers object {"Which framework?": "React"} Facoltativo. Associa il testo della domanda all'etichetta dell'opzione selezionata. Le risposte a selezione multipla uniscono le etichette con virgole. Claude non imposta questo campo; forniscilo tramite updatedInput per rispondere in modo programmatico
ExitPlanMode

Presenta un piano e chiede all'utente di approvarlo prima che Claude esca dal plan mode. Claude scrive il piano in un file su disco prima di chiamare lo strumento, quindi il tool_input letterale del modello è in genere vuoto. Claude Code inserisce il contenuto del piano e il percorso del file prima di passare l'input agli hook.

Campo Tipo Esempio Descrizione
plan string "## Refactor auth\n1. Extract..." Contenuto del piano in Markdown. Inserito dal file del piano su disco
planFilePath string "/Users/.../plans/refactor-auth.md" Percorso del file del piano. Inserito
allowedPrompts array [{"tool": "Bash", "prompt": "run tests"}] Deprecato. Claude Code accetta il campo ma lo ignora. Prima della v2.1.205, conteneva i permessi basati su prompt che Claude richiedeva per implementare il piano

In PostToolUse, tool_response è un oggetto con i campi plan e filePath che contengono il piano approvato, più alcuni flag di stato interni. Leggi tool_response.plan per il contenuto del piano anziché rileggere il file dal disco.

Controllo delle decisioni di PreToolUse

Gli hook PreToolUse possono controllare se una chiamata a uno strumento procede. A differenza di altri hook che usano un campo decision di primo livello, PreToolUse restituisce la sua decisione all'interno di un oggetto hookSpecificOutput. Questo gli offre un controllo più ricco: quattro esiti (allow, deny, ask o defer) più la possibilità di modificare l'input dello strumento prima dell'esecuzione.

Campo Descrizione
permissionDecision "allow" salta la richiesta di permesso, tranne per le azioni che nessuna modalità approva automaticamente e per AskUserQuestion e ExitPlanMode, che necessitano di updatedInput abbinato. "deny" impedisce la chiamata allo strumento. "ask" chiede all'utente di confermare. "defer" esce in modo ordinato così che lo strumento possa essere ripreso in seguito. Le regole di negazione e di richiesta vengono comunque valutate indipendentemente da ciò che restituisce l'hook
permissionDecisionReason Per "ask", mostrato all'utente nella richiesta di permesso. Quando Claude Code nega la chiamata in un'esecuzione -p in cui nessuno può rispondere a quella richiesta, Claude legge invece il motivo nel risultato dello strumento. Per "deny", mostrato a Claude. Per "allow" e "defer", scritto solo nel log di debug
updatedInput Modifica i parametri di input dello strumento prima dell'esecuzione. Sostituisce l'intero oggetto di input, quindi includi i campi invariati insieme a quelli modificati. Claude Code valuta le regole di permesso e l'idoneità al passaggio automatico in background di un comando Bash rispetto all'input restituito dal tuo hook, non all'input inviato da Claude. Combinalo con "allow" per approvare automaticamente, oppure con "ask" per mostrare l'input modificato all'utente. Per "defer", viene ignorato
additionalContext Stringa aggiunta al contesto di Claude insieme al risultato dello strumento. Ignorata quando permissionDecision è "defer". Consulta Aggiungere contesto per Claude

Quando più hook PreToolUse restituiscono decisioni diverse, la precedenza è deny > defer > ask > allow.

Un hook che blocca uscendo con 2 segue lo stesso percorso di "deny": Claude vede il messaggio di stderr come motivo della negazione.

Quando un hook restituisce "ask", la richiesta di permesso mostrata all'utente include un'etichetta che identifica la provenienza dell'hook: [settings] per un hook da qualsiasi file di impostazioni o dal frontmatter di un agente, [plugin:<name>] per l'hook di un plugin, oppure [skill] per un hook dal frontmatter di una skill. Questo aiuta gli utenti a capire quale fonte di configurazione sta richiedendo la conferma.

Un "ask" di un hook forza una richiesta di permesso anche in modalità auto: il classificatore può comunque negare la chiamata allo strumento, ma non può approvarla silenziosamente. Prima della v2.1.211, il classificatore poteva approvare un comando Bash eseguito fuori dalla sandbox senza mostrare la richiesta voluta dall'hook; il classificatore applicava comunque le proprie regole di sicurezza a quel comando, e un "deny" di un hook veniva sempre rispettato.

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "My reason here",
    "updatedInput": {
      "field_to_modify": "new value"
    },
    "additionalContext": "Current environment: production. Proceed with caution."
  }
}

Strumenti che richiedono l'interazione dell'utente

AskUserQuestion e ExitPlanMode richiedono l'interazione dell'utente. In modalità non interattiva con il flag -p, Claude Code li offre solo quando l'esecuzione dispone di un host dei permessi che riceva la richiesta, come un callback canUseTool dell'Agent SDK.

Un hook PreToolUse soddisfa quel requisito quando fa quanto segue:

  1. Legge l'input dello strumento da stdin
  2. Raccoglie la risposta tramite la tua interfaccia utente
  3. Restituisce permissionDecision: "allow" insieme a un updatedInput che contiene la risposta, così lo strumento viene eseguito senza chiedere

Restituire solo "allow" non è sufficiente per questi strumenti.

Per AskUserQuestion, restituisci l'array questions originale e aggiungi un oggetto answers che associa il testo di ogni domanda alla risposta scelta. Questo output risponde a una domanda con React:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "updatedInput": {
      "questions": [
        {
          "question": "Which framework?",
          "header": "Framework",
          "options": [{"label": "React", "description": "Component library"}, {"label": "Vue", "description": "Progressive framework"}],
          "multiSelect": false
        }
      ],
      "answers": {"Which framework?": "React"}
    }
  }
}

Uno strumento MCP che il suo server contrassegna con _meta["anthropic/requiresUserInteraction"] è più restrittivo: un hook non può saltare la sua richiesta di approvazione con "allow", con o senza updatedInput, perché Claude Code non può confermare che l'hook abbia raccolto l'interazione di cui lo strumento ha bisogno.

Differire una chiamata a uno strumento

"defer" è pensato per le integrazioni che eseguono claude -p come sottoprocesso e leggono il suo output JSON, come un'app dell'Agent SDK o un'interfaccia utente personalizzata costruita su Claude Code. Consente a quel processo chiamante di mettere in pausa Claude a una chiamata a uno strumento, raccogliere input tramite la propria interfaccia e riprendere da dove si era interrotto. Claude Code rispetta questo valore solo in modalità non interattiva con il flag -p. Nelle sessioni interattive registra un avviso e ignora il risultato dell'hook.

Lo strumento AskUserQuestion è il caso tipico: Claude vuole chiedere qualcosa all'utente, ma non c'è alcun terminale in cui rispondere. Un'esecuzione -p offre AskUserQuestion solo quando dispone di un host dei permessi, come uno strumento MCP che passi con --permission-prompt-tool, quindi avvia l'esecuzione con uno di essi. Il ciclo completo funziona così:

  1. Claude chiama AskUserQuestion. L'hook PreToolUse viene attivato.
  2. L'hook restituisce permissionDecision: "defer". Lo strumento non viene eseguito. Il processo esce con stop_reason: "tool_deferred" e la chiamata allo strumento in sospeso viene conservata nella trascrizione.
  3. Il processo chiamante legge deferred_tool_use dal risultato dell'SDK, presenta la domanda nella propria interfaccia utente e attende una risposta.
  4. Il processo chiamante esegue claude -p --resume <session-id> con lo stesso host dei permessi. La stessa chiamata allo strumento attiva di nuovo PreToolUse.
  5. L'hook restituisce permissionDecision: "allow" con la risposta in updatedInput. Lo strumento viene eseguito e Claude continua.

Il campo deferred_tool_use contiene id, name e input dello strumento. L'input è costituito dai parametri generati da Claude per la chiamata allo strumento, acquisiti prima dell'esecuzione:

{
  "type": "result",
  "subtype": "success",
  "stop_reason": "tool_deferred",
  "session_id": "abc123",
  "deferred_tool_use": {
    "id": "toolu_01abc",
    "name": "AskUserQuestion",
    "input": { "questions": [{ "question": "Which framework?", "header": "Framework", "options": [{"label": "React", "description": "Component library"}, {"label": "Vue", "description": "Progressive framework"}], "multiSelect": false }] }
  }
}

Non esiste alcun timeout né limite di nuovi tentativi. La sessione rimane su disco finché non la riprendi, salvo la pulizia di conservazione di cleanupPeriodDays, che elimina i file di sessione dopo 30 giorni per impostazione predefinita, secondo le regole della pulizia di conservazione. Se la risposta non è pronta quando riprendi, l'hook può restituire di nuovo "defer" e il processo esce allo stesso modo. Il processo chiamante decide quando interrompere il ciclo restituendo infine "allow" o "deny" dall'hook.

"defer" funziona solo quando Claude effettua una singola chiamata a uno strumento nel turno. Se Claude effettua più chiamate a strumenti contemporaneamente, "defer" viene ignorato con un avviso e lo strumento procede attraverso il normale flusso dei permessi. Il vincolo esiste perché la ripresa può rieseguire un solo strumento: non c'è modo di differire una chiamata di un gruppo senza lasciare irrisolte le altre.

Se lo strumento differito non è più disponibile quando riprendi, il processo esce con stop_reason: "tool_deferred_unavailable" e is_error: true prima che l'hook venga attivato. Questo accade quando un server MCP che forniva lo strumento non è connesso per la sessione ripresa. Il payload deferred_tool_use è comunque incluso, così puoi identificare quale strumento è venuto a mancare.

PermissionRequest

Viene eseguito quando Claude Code sta per chiederti il permesso di usare uno strumento. Nelle sessioni che non possono mostrare una richiesta, come i subagent in background in modalità non interattiva, Claude Code esegue comunque questi hook e, se nessun hook restituisce una decisione, nega la chiamata allo strumento. Per una chiamata che raggiunge un --permission-prompt-tool o il callback canUseTool dell'Agent SDK, gli hook vengono eseguiti insieme al tuo host, e si applica la decisione di chi decide per primo. Usa il controllo delle decisioni di PermissionRequest per consentire o negare per conto dell'utente.

Usa questo evento quando hai bisogno di un segnale nel momento in cui Claude chiede il permesso di usare uno strumento. Claude Code esegue un hook Notification con il tipo permission_prompt solo dopo che la richiesta è rimasta in attesa per circa sei secondi.

Claude Code non esegue gli hook PermissionRequest per la richiesta di rete di un comando in sandbox. Per ottenere un segnale per quella richiesta, usa il tipo di notifica permission_prompt.

Corrisponde sul nome dello strumento, con gli stessi valori di PreToolUse.

Input di PermissionRequest

Gli hook PermissionRequest ricevono i campi tool_name e tool_input come gli hook PreToolUse, ma senza tool_use_id. Per uno strumento MCP, ricevono anche l'oggetto mcp_server. Un array facoltativo permission_suggestions contiene gli aggiornamenti dei permessi che Claude Code suggerisce per questa richiesta, come l'aggiunta di una regola di consenso o la modifica della modalità di permesso.

L'array permission_suggestions non è un elenco esatto delle opzioni che vedi, perché ogni finestra di dialogo dei permessi costruisce le proprie opzioni. Alcune finestre di dialogo, come quella per le modifiche ai file, non leggono affatto l'array e ricavano le loro opzioni dalla richiesta stessa. Una finestra di dialogo che lo legge può comunque nascondere un'opzione il cui suggerimento resta nell'array, ad esempio quando allowManagedPermissionRulesOnly nasconde le opzioni di salvataggio delle regole. Può anche offrire opzioni che non hanno alcuna voce di suggerimento, come Yes, and switch to auto mode, che cambia direttamente la modalità di permesso anziché tramite un aggiornamento dei permessi.

Gli hook PreToolUse vengono eseguiti prima di ogni chiamata a uno strumento, che richieda o meno un permesso. Gli hook PermissionRequest vengono eseguiti solo quando Claude Code sta per chiederti il permesso, oppure quando altrimenti negherebbe automaticamente una chiamata che non può mostrare una richiesta. Nessuno dei due eventi viene attivato per EndConversation.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PermissionRequest",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf node_modules",
    "description": "Remove node_modules directory"
  },
  "permission_suggestions": [
    {
      "type": "addRules",
      "rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
      "behavior": "allow",
      "destination": "localSettings"
    }
  ]
}

Controllo delle decisioni di PermissionRequest

Gli hook PermissionRequest possono consentire o negare le richieste di permesso. Oltre ai campi di output JSON disponibili per tutti gli hook, il tuo script di hook può restituire un oggetto decision con questi campi specifici dell'evento:

Campo Descrizione
behavior "allow" concede il permesso, "deny" lo nega. Le regole di negazione e di richiesta vengono comunque valutate, quindi un hook che restituisce "allow" non sovrascrive una regola di negazione corrispondente
updatedInput Solo per "allow": modifica i parametri di input dello strumento prima dell'esecuzione. Sostituisce l'intero oggetto di input, quindi includi i campi invariati insieme a quelli modificati. L'input modificato viene rivalutato rispetto alle regole di negazione e di richiesta
updatedPermissions Solo per "allow": array di voci di aggiornamento dei permessi da applicare, come l'aggiunta di una regola di consenso o la modifica della modalità di permesso della sessione
message Solo per "deny": comunica a Claude perché il permesso è stato negato
interrupt Solo per "deny": se true, ferma Claude

Un hook che esce con codice 2 senza un oggetto decision lascia invariato il flusso dei permessi, e il suo stderr viene scartato. Per concedere o negare la richiesta, restituisci l'oggetto decision.

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedInput": {
        "command": "npm run lint"
      }
    }
  }
}

Voci di aggiornamento dei permessi

Il campo di output updatedPermissions e il campo di input permission_suggestions usano entrambi lo stesso array di oggetti voce. Ogni voce ha un type che determina i suoi altri campi e una destination che controlla dove viene scritta la modifica.

type Campi Effetto
addRules rules, behavior, destination Aggiunge regole di permesso. rules è un array di oggetti {toolName, ruleContent?}. Ometti ruleContent per corrispondere all'intero strumento. behavior è "allow", "deny" o "ask"
replaceRules rules, behavior, destination Sostituisce tutte le regole del behavior indicato nella destination con le rules fornite
removeRules rules, behavior, destination Rimuove le regole corrispondenti del behavior indicato
setMode mode, destination Cambia la modalità di permesso. Le modalità valide sono default, auto, acceptEdits, dontAsk, bypassPermissions, plan e manual come alias di default
addDirectories directories, destination Aggiunge directory di lavoro. directories è un array di stringhe di percorso
removeDirectories directories, destination Rimuove directory di lavoro

Il campo destination di ogni voce determina se la modifica resta in memoria o viene salvata in modo persistente in un file di impostazioni.

destination Scrive in
session solo in memoria, scartata quando la sessione termina
localSettings .claude/settings.local.json
projectSettings .claude/settings.json
userSettings ~/.claude/settings.json

Un hook può restituire uno dei permission_suggestions ricevuti come proprio output updatedPermissions.

PostToolUse

Viene eseguito immediatamente dopo che uno strumento è stato completato con successo.

Corrisponde sul nome dello strumento, con gli stessi valori di PreToolUse.

Usa una corrispondenza più ampia quando il nome dello strumento non è il filtro giusto:

  • Per eseguire un hook dopo che qualsiasi strumento è stato completato con successo, ometti il matcher o impostalo su "*". Il tuo hook può quindi scoprire da solo cosa è cambiato, ad esempio eseguendo git status --porcelain, che elenca anche i file non tracciati che git diff non rileva. Per le chiamate agli strumenti che falliscono, aggiungi lo stesso hook sotto PostToolUseFailure.
  • Per eseguire un hook quando un file specifico cambia su disco, indipendentemente da chi lo abbia scritto, usa FileChanged. Claude Code non esegue un hook PostToolUse con corrispondenza Edit|Write quando un comando Bash o un processo esterno a Claude Code riscrive lo stesso file.

Input di PostToolUse

Gli hook PostToolUse si attivano dopo che uno strumento è già stato eseguito con successo. L'input include sia tool_input, gli argomenti inviati allo strumento, sia tool_response, il risultato che ha restituito. Lo schema esatto di entrambi dipende dallo strumento. I percorsi in tool_input degli strumenti per i file arrivano nello stesso formato di PreToolUse: sempre assoluti, con i separatori nativi della piattaforma, quindi barre rovesciate su Windows. Per uno strumento MCP, l'input contiene anche l'oggetto mcp_server.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.txt",
    "content": "file content"
  },
  "tool_response": {
    "filePath": "/path/to/file.txt",
    "type": "create"
  },
  "tool_use_id": "toolu_01ABC123...",
  "duration_ms": 12
}
Campo Descrizione
duration_ms Facoltativo. Tempo di esecuzione dello strumento in millisecondi. Esclude il tempo trascorso nelle richieste di permesso e negli hook PreToolUse

Controllo delle decisioni di PostToolUse

Gli hook PostToolUse possono fornire feedback a Claude dopo l'esecuzione dello strumento. Oltre ai campi di output JSON disponibili per tutti gli hook, il tuo script di hook può restituire questi campi specifici dell'evento:

Campo Descrizione
decision "block" aggiunge il reason accanto al risultato dello strumento. Claude vede comunque l'output originale; per sostituirlo, usa updatedToolOutput
reason Spiegazione mostrata a Claude quando decision è "block"
additionalContext Stringa aggiunta al contesto di Claude insieme al risultato dello strumento. Consulta Aggiungere contesto per Claude
classifierContext Breve nota sul risultato di questa chiamata destinata al classificatore della modalità auto anziché a Claude. Consulta Annotare un risultato per il classificatore della modalità auto. Richiede Claude Code v2.1.236 o successiva
updatedToolOutput Sostituisce l'output dello strumento con il valore fornito prima che venga inviato a Claude. Il valore deve corrispondere alla struttura dell'output dello strumento
updatedMCPToolOutput Sostituisce l'output solo per gli strumenti MCP. Preferisci updatedToolOutput, che funziona per tutti gli strumenti

L'esempio seguente sostituisce l'output di una chiamata Bash. Il valore sostitutivo corrisponde alla struttura dell'output dello strumento Bash:

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "Additional information for Claude",
    "updatedToolOutput": {
      "stdout": "[redacted]",
      "stderr": "",
      "interrupted": false,
      "isImage": false
    }
  }
}

Annotare un risultato per il classificatore della modalità auto

Restituisci classifierContext per inviare una breve nota sul risultato della chiamata allo strumento al classificatore della modalità auto anziché a Claude. Il classificatore non riceve mai i risultati degli strumenti in sé, quindi questo campo è il modo supportato per comunicargli qualcosa su ciò che una chiamata ha restituito prima che esamini le azioni successive. Il campo richiede Claude Code v2.1.236 o successiva.

L'esempio seguente indica al classificatore da dove proviene l'output di una query:

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "classifierContext": "This query ran against the staging database, not production."
  }
}

Il peso che il classificatore attribuisce alla nota dipende da dove hai configurato l'hook:

  • Hook configurati in Claude Code: per gli hook provenienti da file di impostazioni, plugin, skill e frontmatter degli agenti, il classificatore tratta la nota come contesto non verificato fornito dall'applicazione. La nota non stabilisce mai l'intento dell'utente e, se afferma che hai approvato o richiesto qualcosa, il classificatore verifica tale affermazione confrontandola con i tuoi messaggi nella conversazione
  • Callback in-process dell'Agent SDK: quando un'applicazione che incorpora Claude Code registra l'hook come callback del TypeScript SDK e restituisce la nota durante la sessione attiva, il classificatore può considerare come intento dell'utente una dichiarazione dell'utente riportata nella nota. Tale dichiarazione può soddisfare un requisito di consenso che il classificatore accetterebbe da un messaggio che invii tu, ma non rimuove mai un blocco che nemmeno un tuo messaggio potrebbe rimuovere. Dopo la ripresa di una sessione, Claude Code tratta le note ripristinate come contesto non verificato. Quando hook di entrambi i gruppi annotano la stessa chiamata, il classificatore tratta la nota combinata come non verificata

Claude Code applica questi limiti quando consegna la nota:

  • Lunghezza: Claude Code limita le note per una singola chiamata a uno strumento a 2.000 caratteri e tronca il resto. Il limite è condiviso tra tutti gli hook che rispondono a quella chiamata
  • Solo risposte sincrone: Claude Code ignora il campo nella risposta di un hook che viene eseguito in background, perché quella risposta arriva dopo che Claude Code ha registrato il risultato dello strumento
  • Chiamate che il classificatore non registra: la trascrizione del classificatore omette le consultazioni di sola lettura, come le letture di file e le ricerche. Claude Code scarta una nota associata a una di queste chiamate
  • Interazione con le riscritture: quando la nota descrive un output che stai sostituendo con updatedToolOutput, restituisci entrambi i campi nella stessa risposta dell'hook. Claude Code scarta la nota se quella riscrittura viene rifiutata o se la riscrittura di un altro hook la sostituisce. Claude Code consegna una nota restituita senza riscrittura anche quando un altro hook riscrive l'output

PostToolUseFailure

Viene eseguito quando uno strumento che ha iniziato l'esecuzione fallisce: lo strumento ha generato un errore, oppure uno strumento MCP ha restituito un risultato di errore. Usalo per registrare i fallimenti, inviare avvisi o fornire feedback correttivo a Claude.

Corrisponde sul nome dello strumento, con gli stessi valori di PreToolUse.

Input di PostToolUseFailure

Gli hook PostToolUseFailure ricevono gli stessi campi tool_name e tool_input di PostToolUse, insieme alle informazioni sull'errore come campi di primo livello. Per uno strumento MCP, ricevono anche l'oggetto mcp_server. Ad esempio, un comando npm test fallito potrebbe fornire:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolUseFailure",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test",
    "description": "Run test suite"
  },
  "tool_use_id": "toolu_01ABC123...",
  "error": "Exit code 1\nError: Cannot find module 'express'",
  "is_interrupt": false,
  "duration_ms": 4187
}
Campo Descrizione
error Stringa che descrive cosa è andato storto. Il formato dipende dallo strumento che ha fallito
is_interrupt Booleano facoltativo. True quando il fallimento ha raggiunto Claude Code come interruzione anziché come errore segnalato dallo strumento. Annullare uno strumento in esecuzione non attiva questo hook; il risultato dello strumento contiene invece il messaggio di interruzione
duration_ms Facoltativo. Tempo di esecuzione dello strumento in millisecondi. Esclude il tempo trascorso nelle richieste di permesso e negli hook PreToolUse

La stringa error è generalmente lo stesso testo che Claude riceve come risultato dello strumento fallito. Il suo formato varia in base allo strumento e al tipo di fallimento. Basa il tuo hook su tool_name, is_interrupt e sulla prima riga Exit code N; tratta il resto della stringa come testo di visualizzazione, non come un formato stabile.

  • Per Bash e PowerShell, un comando che è stato eseguito ed è terminato produce una prima riga Exit code N, seguita da qualsiasi output prodotto dal comando come un unico blocco con stdout e stderr intercalati
  • Un payload può anche contenere un semplice messaggio di fallimento senza riga del codice di uscita, quando Claude Code non è riuscito ad avviare il processo della shell stesso
  • Claude Code tronca al centro le stringhe lunghe attorno a un marcatore ... [N characters truncated] ... e può inserire righe proprie, come Command timed out after 2m 0s

Controllo delle decisioni di PostToolUseFailure

Gli hook PostToolUseFailure possono fornire contesto a Claude dopo il fallimento di uno strumento. Oltre ai campi di output JSON disponibili per tutti gli hook, il tuo script di hook può restituire questi campi specifici dell'evento:

Campo Descrizione
additionalContext Stringa aggiunta al contesto di Claude insieme all'errore. Consulta Aggiungere contesto per Claude
{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUseFailure",
    "additionalContext": "Additional information about the failure for Claude"
  }
}

PostToolBatch

Viene eseguito una volta dopo che ogni chiamata a uno strumento in un batch è stata risolta, prima che Claude Code invii la richiesta successiva al modello. PostToolUse si attiva una volta per strumento, il che significa che si attiva in modo concorrente quando Claude effettua chiamate agli strumenti in parallelo. PostToolBatch si attiva esattamente una volta con l'intero batch, quindi è il posto giusto per iniettare contesto che dipende dall'insieme di strumenti eseguiti anziché da un singolo strumento. Non esiste un matcher per questo evento.

Input di PostToolBatch

Oltre ai campi di input comuni, gli hook PostToolBatch ricevono tool_calls, un array che descrive ogni chiamata a uno strumento nel batch:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolBatch",
  "tool_calls": [
    {
      "tool_name": "Read",
      "tool_input": {"file_path": "/.../ledger/accounts.py"},
      "tool_use_id": "toolu_01...",
      "tool_response": "1\tfrom __future__ import annotations\n2\t..."
    },
    {
      "tool_name": "Read",
      "tool_input": {"file_path": "/.../ledger/transactions.py"},
      "tool_use_id": "toolu_02...",
      "tool_response": "1\tfrom __future__ import annotations\n2\t..."
    }
  ]
}

tool_response contiene lo stesso contenuto che il modello riceve nel blocco tool_result corrispondente. Il valore è una stringa serializzata o un array di blocchi di contenuto, esattamente come lo ha emesso lo strumento. Per Read, ciò significa testo con il numero di riga come prefisso anziché il contenuto grezzo del file. Le risposte possono essere grandi, quindi analizza solo i campi di cui hai bisogno.

Controllo delle decisioni di PostToolBatch

Gli hook PostToolBatch possono iniettare contesto per Claude. Oltre ai campi di output JSON disponibili per tutti gli hook, il tuo script di hook può restituire questi campi specifici dell'evento:

Campo Descrizione
additionalContext Stringa di contesto iniettata una volta prima della chiamata successiva al modello. Consulta Aggiungere contesto per Claude per i dettagli sulla consegna, su cosa inserirvi e su come le sessioni riprese gestiscono i valori passati
{
  "hookSpecificOutput": {
    "hookEventName": "PostToolBatch",
    "additionalContext": "These files are part of the ledger module. Run pytest before marking the task complete."
  }
}

Restituire decision: "block" o continue: false arresta il ciclo agentico prima della chiamata successiva al modello. Il messaggio di blocco proviene dal reason o dallo stopReason JSON, oppure da stderr con uscita 2. Lo vedi come avviso nella trascrizione, e rimane nella conversazione, quindi Claude lo vede quando la conversazione continua.

PermissionDenied

Viene eseguito quando la modalità auto rifiuta una chiamata a uno strumento, anche quando la rifiuta senza un verdetto del classificatore perché un controllo di sicurezza separato dalla modalità auto ha rifiutato la richiesta del classificatore stesso o la sua risposta non è stata interpretabile. Questo hook si attiva solo in modalità auto: non viene eseguito quando rifiuti manualmente una finestra di dialogo di permesso, quando un hook PreToolUse blocca una chiamata o quando corrisponde una regola deny. Usalo per registrare i rifiuti, modificare la configurazione o comunicare al modello che può riprovare la chiamata allo strumento.

Corrisponde sul nome dello strumento, con gli stessi valori di PreToolUse.

Input di PermissionDenied

Oltre ai campi di input comuni, gli hook PermissionDenied ricevono tool_name, tool_input, tool_use_id e reason. Per uno strumento MCP, ricevono anche l'oggetto mcp_server.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "auto",
  "hook_event_name": "PermissionDenied",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf /tmp/build",
    "description": "Clean build directory"
  },
  "tool_use_id": "toolu_01ABC123...",
  "reason": "[Irreversible Local Destruction]"
}
Campo Descrizione
reason Il motivo del rifiuto. Per un verdetto del classificatore, nella maggior parte delle sessioni indica la regola corrispondente tra parentesi quadre, come [Data Exfiltration]; consulta Esaminare i rifiuti per le altre forme. Per un rifiuto senza verdetto, inizia con Auto mode could not evaluate this action and is blocking it for safety. Per un rifiuto dovuto all'indisponibilità del modello classificatore, è il testo fisso Classifier unavailable

Controllo delle decisioni di PermissionDenied

Gli hook PermissionDenied possono comunicare al modello che può riprovare la chiamata allo strumento rifiutata. Restituisci un oggetto JSON con hookSpecificOutput.retry impostato su true:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionDenied",
    "retry": true
  }
}

Quando retry è true, Claude Code aggiunge un messaggio alla conversazione che comunica al modello che può riprovare la chiamata allo strumento. Claude Code non annulla il rifiuto stesso. Se il tuo hook non restituisce JSON, o restituisce retry: false, il rifiuto resta valido e il modello riceve il messaggio di rifiuto originale.

Claude Code ignora retry: true quando il classificatore non ha prodotto alcun verdetto sull'azione: la sua risposta non è stata interpretabile, oppure un controllo di sicurezza separato dalla modalità auto ha rifiutato la richiesta del classificatore stesso. Per questi rifiuti, Claude Code indica già al modello nel messaggio di rifiuto se riprovare più tardi o andare avanti.

Notification

Viene eseguito quando Claude Code invia notifiche. Corrisponde sul tipo di notifica. Ometti il matcher per eseguire gli hook per tutti i tipi di notifica.

Ricevi questi eventi di hook anche con le notifiche desktop disattivate: l'impostazione preferredNotifChannel, incluso notifications_disabled, cambia solo il modo in cui vieni avvisato, non se il tuo hook viene eseguito.

Matcher Quando si attiva
permission_prompt Claude ha bisogno che tu approvi l'uso di uno strumento o la richiesta di rete di un comando in sandbox, e la richiesta è in attesa da circa sei secondi
idle_prompt Claude ha finito di rispondere circa 60 secondi fa e da allora non hai digitato nulla
auth_success L'autenticazione viene completata
elicitation_dialog Un server MCP apre un modulo di elicitazione e non digiti da circa sei secondi
elicitation_url_dialog Un server MCP ti chiede di aprire un URL nel browser e non digiti da circa sei secondi
elicitation_complete Un server MCP segnala che un'elicitazione in modalità URL è completata
elicitation_response Una risposta di elicitazione MCP viene inviata al server
agent_needs_input Una sessione in background inizia ad attendere un tuo input mentre la vista agenti è aperta in un terminale. Si attiva anche quando una sessione del terminale ti mostra una domanda di configurazione del terminale di un membro di un team di agenti o l'avviso della modalità auto sugli addebiti per le richieste del classificatore e non digiti da circa sei secondi
agent_completed Una sessione in background termina o fallisce. Si attiva solo mentre la vista agenti è aperta in un terminale
quota_auto_resume_fired Claude Code riprende la tua attività dopo che un limite di utilizzo di claude.ai l'aveva messa in pausa: al momento del ripristino, oppure prima quando qualcosa che fai in Claude Code durante l'attesa, come aggiungere crediti di utilizzo, passare a un piano superiore o cambiare modello, rende di nuovo disponibile l'utilizzo, con l'eccezione relativa all'impostazione del modello
quota_auto_resume_stale Un limite di utilizzo di claude.ai è stato ripristinato mentre il tuo computer era in sospensione per più di circa 30 minuti. Claude Code attende che tu prema Enter invece di continuare. Dopo una sospensione più breve continua e attiva invece quota_auto_resume_fired
quota_auto_resume_disabled Claude Code termina l'attesa per un limite di utilizzo di claude.ai senza riprendere la tua attività: autoContinueAtUsageLimit è stato disattivato o il ripristino si è spostato a più di 24 ore di distanza durante un'attesa avviata da Claude Code in autonomia, l'attività ripresa ha continuato a raggiungere il limite, oppure la ripresa è stata bloccata prima di raggiungere il modello. Non si attiva quando premi Esc o Ctrl+C, o scegli Don't continue automatically

I tipi quota_auto_resume_fired, quota_auto_resume_stale e quota_auto_resume_disabled richiedono Claude Code v2.1.234 o successiva.

Nelle sessioni del terminale, permission_prompt per la richiesta di rete di un comando in sandbox richiede Claude Code v2.1.246 o successiva.

agent_needs_input per la domanda di configurazione del terminale di un membro del team richiede Claude Code v2.1.248 o successiva.

Claude Code calcola la tempistica di permission_prompt in modo diverso nelle sessioni in cui invia le richieste di permesso alla callback canUseTool dell'Agent SDK, che è il modo in cui Claude Desktop e l'estensione VS Code ospitano Claude Code:

  • Aspettati permission_prompt circa sei secondi dopo che Claude chiede il permesso. Claude Code non lo rinvia mentre digiti.
  • Se tu o un hook PermissionRequest rispondete prima, Claude Code non esegue permission_prompt.
  • Imposta CLAUDE_CODE_DISABLE_PERMISSION_PROMPT_NOTIFY_HOOKS su 1 per disattivare permission_prompt in queste sessioni.

Prima della v2.1.233, permission_prompt non si attivava in queste sessioni.

Usa matcher separati per eseguire gestori diversi a seconda del tipo di notifica. Questa configurazione attiva uno script di avviso specifico per i permessi quando Claude ha bisogno di un'approvazione dei permessi e una notifica diversa quando Claude è inattivo:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/permission-alert.sh"
          }
        ]
      },
      {
        "matcher": "idle_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/idle-notification.sh"
          }
        ]
      }
    ]
  }
}

Input di Notification

Oltre ai campi di input comuni, gli hook Notification ricevono message con il testo della notifica, un title facoltativo e notification_type, che indica quale tipo si è attivato.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Notification",
  "message": "Claude needs your permission",
  "title": "Permission needed",
  "notification_type": "permission_prompt"
}

Gli hook Notification non possono bloccare o modificare le notifiche. Claude Code scarta i loro campi systemMessage e continue ma emette comunque terminalSequence, su cui si basa l'esempio di notifica desktop. Gli hook Notification sono pensati per effetti collaterali come l'inoltro della notifica a un servizio esterno.

SubagentStart

Viene eseguito quando Claude genera un subagent con lo strumento Agent, quando Claude riprende un subagent, e ogni volta che un membro in-process di un team di agenti gestisce un nuovo messaggio. Supporta i matcher per filtrare in base al nome del tipo di agente. Per gli agenti integrati, è il nome dell'agente, come general-purpose, Explore o Plan. Per i subagent personalizzati, è il campo name del frontmatter dell'agente, non il nome del file.

Per i subagent forniti da un plugin, il tipo di agente è l'identificatore con ambito del plugin, come my-plugin:reviewer, non il semplice nome del frontmatter. I due punti collocano un nome con ambito del plugin sul percorso delle espressioni regolari, quindi ancora il matcher con ^ e $ per una corrispondenza esatta: ^my-plugin:reviewer$.

Input di SubagentStart

Oltre ai campi di input comuni, gli hook SubagentStart ricevono agent_id con l'identificatore univoco del subagent e agent_type con il nome dell'agente su cui filtra il matcher.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SubagentStart",
  "agent_id": "agent-abc123",
  "agent_type": "Explore"
}

Gli hook SubagentStart non possono bloccare la creazione del subagent, ma possono iniettare contesto nel subagent. Oltre ai campi di output JSON disponibili per tutti gli hook, puoi restituire:

Campo Descrizione
additionalContext Stringa aggiunta al contesto del subagent all'inizio della sua conversazione, prima del suo primo prompt. Consulta Aggiungere contesto per Claude
{
  "hookSpecificOutput": {
    "hookEventName": "SubagentStart",
    "additionalContext": "Follow security guidelines for this task"
  }
}

Quando l'hook viene eseguito di nuovo per lo stesso subagent, Claude Code inietta il contesto restituito solo quando il contesto del subagent non contiene già la copia di un'esecuzione precedente. La copia iniettata all'avvio rimane al suo posto, lasciando intatta la cache del prompt del subagent. Dopo che la compattazione automatica scarta quella copia, Claude Code inietta di nuovo il contesto dell'esecuzione successiva.

SubagentStop

Viene eseguito quando un subagent di Claude Code ha finito di rispondere. Corrisponde sul tipo di agente, con gli stessi valori di SubagentStart.

Input di SubagentStop

Oltre ai campi di input comuni, gli hook SubagentStop ricevono stop_hook_active, agent_id, agent_type, agent_transcript_path e last_assistant_message. Il campo agent_type è il valore usato per il filtraggio del matcher. Il transcript_path è la trascrizione della sessione principale, mentre agent_transcript_path è la trascrizione del subagent stesso, archiviata in una cartella annidata subagents/. Il campo last_assistant_message contiene il contenuto testuale della risposta finale del subagent, così gli hook possono accedervi senza analizzare il file della trascrizione.

Non tutti gli eventi SubagentStop provengono da un subagent generato da Claude. Claude Code esegue anche agenti interni per alcune delle sue funzionalità, come i suggerimenti di prompt e le domande laterali /btw, e SubagentStop si attiva anche quando uno di questi termina. Per questi eventi, agent_type è il nome dell'agente con cui viene eseguita la sessione stessa, come quello impostato con --agent o con l'impostazione agent, ed è una stringa vuota quando la sessione viene eseguita senza.

Un matcher che nomina tipi di agente non corrisponde a un agent_type vuoto. Un hook il cui matcher è omesso, "" o "*", oppure è un'espressione regolare che corrisponde a una stringa vuota, viene eseguito anche per gli eventi con un agent_type vuoto.

Su Claude Code v2.1.271 o successiva, un subagent eseguito con lo strumento SubagentHandback consegna il proprio resoconto tramite quello strumento prima di fermarsi. Il campo last_assistant_message contiene quindi il testo conclusivo del subagent, se presente, che non è il resoconto consegnato. Il resoconto è l'input message di quella chiamata, che un hook PreToolUse o PostToolUse con corrispondenza su SubagentHandback riceve come tool_input.message.

Gli hook SubagentStop ricevono anche gli array background_tasks e session_crons descritti in Input di Stop. Entrambi gli array hanno come ambito la sessione padre, non il subagent.

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../abc123.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "SubagentStop",
  "stop_hook_active": false,
  "agent_id": "def456",
  "agent_type": "Explore",
  "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl",
  "last_assistant_message": "Analysis complete. Found 3 potential issues...",
  "background_tasks": [],
  "session_crons": []
}

Gli hook SubagentStop usano lo stesso formato di controllo delle decisioni degli hook Stop, incluso hookSpecificOutput.additionalContext con hookEventName impostato su "SubagentStop", per un feedback non di errore che mantiene in esecuzione il subagent. Restituire decision: "block" con un reason mantiene in esecuzione il subagent e consegna reason al subagent come sua istruzione successiva. Un hook che blocca uscendo con 2 consegna il suo messaggio stderr nello stesso modo. Per iniettare contesto nella sessione padre dopo che un subagent ha restituito il controllo, usa invece un hook PostToolUse sullo strumento Agent.

TaskCreated

Viene eseguito quando un'attività viene creata tramite lo strumento TaskCreate. Usalo per imporre convenzioni di denominazione, richiedere descrizioni delle attività o impedire la creazione di determinate attività. In una sessione senza gli strumenti Task, questo evento non si attiva.

Gli hook TaskCreated non supportano i matcher e si attivano a ogni occorrenza.

Input di TaskCreated

Oltre ai campi di input comuni, gli hook TaskCreated ricevono task_id, task_subject e, facoltativamente, task_description, teammate_name e team_name.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "TaskCreated",
  "task_id": "task-001",
  "task_subject": "Implement user authentication",
  "task_description": "Add login and signup endpoints",
  "teammate_name": "implementer",
  "team_name": "session-a1b2c3d4"
}
Campo Descrizione
task_id Identificatore dell'attività in fase di creazione
task_subject Titolo dell'attività
task_description Descrizione dettagliata dell'attività. Può essere assente
teammate_name Nome del membro del team che crea l'attività. Può essere assente
team_name Deprecato. Nome del team derivato dalla sessione; verrà rimosso in una versione futura
agent_id In questo evento, il campo di input comune identifica il subagent o il membro del team in-process che crea l'attività. Può essere assente. Richiede Claude Code v2.1.290 o successiva

Controllo delle decisioni di TaskCreated

Un hook TaskCreated può bloccare la creazione con il codice di uscita 2 o con una decisione JSON. In entrambi i casi, Claude Code elimina il task e restituisce il tuo messaggio a Claude come errore dello strumento. Claude Code ignora continue: false da questo evento e Claude continua a lavorare.

  • Codice di uscita 2: Claude Code restituisce il testo di stderr come messaggio.
  • JSON {"decision": "block", "reason": "..."}: Claude Code restituisce reason come messaggio.

Questo esempio blocca le attività i cui oggetti non seguono il formato richiesto:

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')

if [[ ! "$TASK_SUBJECT" =~ ^\[TICKET-[0-9]+\] ]]; then
  echo "Task subject must start with a ticket number, e.g. '[TICKET-123] Add feature'" >&2
  exit 2
fi

exit 0

TaskCompleted

Viene eseguito quando un'attività viene contrassegnata come completata. Si attiva in due situazioni: quando un agente qualsiasi contrassegna esplicitamente un'attività come completata tramite lo strumento TaskUpdate, oppure quando un membro di un team di agenti termina il proprio turno con attività in corso. Usalo per imporre criteri di completamento, come il superamento dei test o dei controlli lint, prima che un'attività possa essere chiusa.

Gli hook TaskCompleted non supportano i matcher e si attivano a ogni occorrenza.

Input di TaskCompleted

Oltre ai campi di input comuni, gli hook TaskCompleted ricevono task_id, task_subject e, facoltativamente, task_description, teammate_name e team_name.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "TaskCompleted",
  "task_id": "task-001",
  "task_subject": "Implement user authentication",
  "task_description": "Add login and signup endpoints",
  "teammate_name": "implementer",
  "team_name": "session-a1b2c3d4"
}
Campo Descrizione
task_id Identificatore dell'attività in fase di completamento
task_subject Titolo dell'attività
task_description Descrizione dettagliata dell'attività. Può essere assente
teammate_name Nome del membro del team che completa l'attività. Può essere assente
team_name Deprecato. Nome del team derivato dalla sessione; verrà rimosso in una versione futura
agent_id In questo evento, il campo di input comune identifica il subagent o il membro del team in-process che completa l'attività. Può essere assente. Richiede Claude Code v2.1.290 o successiva

Controllo delle decisioni di TaskCompleted

Gli hook TaskCompleted supportano due modi per controllare il completamento delle attività:

  • Codice di uscita 2: l'attività non viene contrassegnata come completata e il messaggio di stderr viene restituito al modello come feedback.
  • JSON {"continue": false, "stopReason": "..."}: quando l'evento è stato attivato da un membro del team che termina il proprio turno, arresta completamente il membro del team, in modo analogo al comportamento dell'hook Stop. Lo stopReason viene mostrato all'utente. Quando l'evento è stato attivato dallo strumento TaskUpdate, Claude Code ignora continue: false; il codice di uscita 2 blocca comunque il completamento.

Questo esempio esegue i test e blocca il completamento dell'attività se falliscono:

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')

# Run the test suite
if ! npm test 2>&1; then
  echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
  exit 2
fi

exit 0

Stop

Viene eseguito quando l'agente principale di Claude Code ha finito di rispondere. Non viene eseguito se l'arresto è avvenuto a causa di un'interruzione da parte dell'utente. Gli errori API attivano invece StopFailure.

Input di Stop

Oltre ai campi di input comuni, gli hook Stop ricevono stop_hook_active, last_assistant_message, background_tasks e session_crons. Il campo stop_hook_active è true quando Claude Code sta già continuando a causa di un hook stop. Controlla questo valore o elabora la trascrizione per evitare di bloccare su una condizione che non si risolverà mai.

Claude Code applica un limite di 8 continuazioni consecutive: dopo che gli hook stop hanno fatto continuare il turno otto volte di seguito, Claude Code sovrascrive il blocco successivo e termina il turno. Il conteggio delle continuazioni consecutive si azzera ogni volta che Claude chiama uno strumento. Per aumentare il limite, imposta CLAUDE_CODE_STOP_HOOK_BLOCK_CAP.

Il campo last_assistant_message contiene il contenuto testuale della risposta finale di Claude, così gli hook possono accedervi senza analizzare il file della trascrizione. Per gli hook che agiscono sul turno appena completato, come gli hook di lettura ad alta voce o di notifica, usa questo campo anziché leggere transcript_path: non è garantito che il file della trascrizione includa il messaggio finale al momento di Stop in tutte le versioni.

Gli array background_tasks e session_crons permettono agli hook di distinguere tra "la sessione è terminata" e "la sessione è in pausa in attesa che un lavoro in background la riattivi". Entrambi gli array sono presenti quando il registro delle attività è raggiungibile e sono vuoti quando non c'è nulla in corso o pianificato.

Ogni voce in background_tasks descrive un'attività in corso e usa questi campi:

Campo Descrizione
id Identificatore dell'attività
type Etichetta descrittiva del tipo di attività, come shell, subagent, monitor, workflow, teammate, cloud session o MCP task. Ogni etichetta identifica quale funzionalità di Claude Code ha creato l'attività. Ricade sul discriminante grezzo per i tipi non riconosciuti
status Stato attuale dell'attività
description Descrizione in testo libero, limitata a 1000 caratteri con un marcatore … [+N chars] nella stringa quando viene troncata
command Riga di comando della shell, limitata a 1000 caratteri. Presente solo per le attività shell
agent_type Nome del tipo di subagent. Presente solo per le attività subagent
server Nome del server MCP. Presente solo per le attività monitor e MCP task
tool Nome dello strumento MCP. Presente solo per le attività monitor e MCP task
name Nome del workflow. Presente solo per le attività workflow

Ogni voce in session_crons descrive un risveglio pianificato con ambito di sessione, proveniente da CronCreate, ScheduleWakeup e /loop:

Campo Descrizione
id Identificatore dell'attività cron
schedule Espressione cron, ad esempio 0 9 * * 1-5
recurring false per i risvegli singoli la cui pianificazione codifica un unico momento di attivazione, true per le attività che si riattivano a ogni corrispondenza
prompt Prompt inviato quando il cron si attiva, limitato a 1000 caratteri con lo stesso marcatore … [+N chars]

Questo esempio mostra un input di Stop con un'attività shell in corso e un cron ricorrente:

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "Stop",
  "stop_hook_active": true,
  "last_assistant_message": "I've completed the refactoring. Here's a summary...",
  "background_tasks": [
    {
      "id": "task-001",
      "type": "shell",
      "status": "running",
      "description": "tail logs",
      "command": "tail -f /var/log/syslog"
    }
  ],
  "session_crons": [
    {
      "id": "cron-001",
      "schedule": "0 9 * * 1-5",
      "recurring": true,
      "prompt": "check the build"
    }
  ]
}

Controllo delle decisioni di Stop

Gli hook Stop e SubagentStop possono controllare se Claude continua. Oltre ai campi di output JSON disponibili per tutti gli hook, il tuo script di hook può restituire questi campi specifici dell'evento:

Campo Descrizione
decision "block" impedisce a Claude di fermarsi. Omettilo per consentire a Claude di fermarsi
reason Obbligatorio quando decision è "block". Indica a Claude perché dovrebbe continuare
hookSpecificOutput.additionalContext Feedback non di errore per Claude. La conversazione continua così Claude può agire di conseguenza, ma a differenza di decision: "block" viene mostrato nella trascrizione come feedback dell'hook anziché come errore dell'hook

Un hook che blocca uscendo con 2 segue lo stesso percorso di reason: Claude riceve il messaggio di stderr come spiegazione del motivo per cui dovrebbe continuare.

{
  "decision": "block",
  "reason": "Must be provided when Claude is blocked from stopping"
}

Usa additionalContext quando l'hook funziona come previsto e fornisce indicazioni a Claude, come "esegui la suite di test prima di finire". Mantiene attiva la conversazione tramite le stesse protezioni dai cicli di decision: "block", ovvero l'input stop_hook_active e il limite di 8 continuazioni consecutive, ma la trascrizione lo etichetta come Stop hook feedback e non viene mostrata alcuna notifica di errore dell'hook:

{
  "hookSpecificOutput": {
    "hookEventName": "Stop",
    "additionalContext": "Please run the test suite before finishing"
  }
}

StopFailure

Viene eseguito al posto di Stop quando il turno termina a causa di un errore API. Claude Code ignora l'output e il codice di uscita dell'hook, a parte terminalSequence. Usalo per registrare i fallimenti, inviare avvisi o intraprendere azioni di ripristino quando Claude non riesce a completare una risposta a causa di rate limit, problemi di autenticazione o altri errori API.

Input di StopFailure

Oltre ai campi di input comuni, gli hook StopFailure ricevono error, error_details facoltativo e last_assistant_message facoltativo. Il campo error identifica il tipo di errore e viene usato per il filtraggio del matcher.

Campo Descrizione
error Tipo di errore: rate_limit, overloaded, authentication_failed, oauth_org_not_allowed, account_on_hold, billing_error, invalid_request, model_not_found, server_error, max_output_tokens, cloud_credential_error o unknown
error_details Dettagli aggiuntivi sull'errore, quando disponibili
last_assistant_message Il testo dell'errore visualizzato nella conversazione. A differenza di Stop e SubagentStop, dove questo campo contiene l'output conversazionale di Claude, per StopFailure contiene la stringa dell'errore API stessa, come "API Error: Rate limit reached"
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "StopFailure",
  "error": "rate_limit",
  "error_details": "429 Too Many Requests",
  "last_assistant_message": "API Error: Rate limit reached"
}

Gli hook StopFailure non hanno controllo delle decisioni. Vengono eseguiti solo a scopo di notifica e di log.

TeammateIdle

Viene eseguito quando un membro di un team di agenti sta per diventare inattivo dopo aver terminato il proprio turno. Usalo per imporre controlli di qualità prima che un membro del team smetta di lavorare, come richiedere il superamento dei controlli lint o verificare che i file di output esistano.

Gli hook TeammateIdle non supportano i matcher e si attivano a ogni occorrenza.

Input di TeammateIdle

Oltre ai campi di input comuni, gli hook TeammateIdle ricevono teammate_name e team_name.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "TeammateIdle",
  "teammate_name": "researcher",
  "team_name": "session-a1b2c3d4"
}
Campo Descrizione
teammate_name Nome del membro del team che sta per diventare inattivo
team_name Deprecato. Nome del team derivato dalla sessione; verrà rimosso in una versione futura
agent_id In questo evento, il campo di input comune identifica il membro del team in-process che sta per diventare inattivo. Può essere assente. Richiede Claude Code v2.1.290 o successiva

Controllo delle decisioni di TeammateIdle

Gli hook TeammateIdle supportano due modi per controllare il comportamento dei membri del team:

  • Codice di uscita 2: il membro del team riceve il messaggio di stderr come feedback e continua a lavorare invece di diventare inattivo.
  • JSON {"continue": false, "stopReason": "..."}: arresta completamente il membro del team, in modo analogo al comportamento dell'hook Stop. Lo stopReason viene mostrato all'utente.

Questo esempio verifica che un artefatto di build esista prima di consentire a un membro del team di diventare inattivo:

#!/bin/bash

if [ ! -f "./dist/output.js" ]; then
  echo "Build artifact missing. Run the build before stopping." >&2
  exit 2
fi

exit 0

ConfigChange

Viene eseguito quando un file di configurazione cambia durante una sessione. Usalo per controllare le modifiche alle impostazioni, applicare criteri di sicurezza o bloccare modifiche non autorizzate ai file di configurazione.

Claude Code esegue gli hook ConfigChange quando cambia un file di impostazioni, un file di criteri gestiti o un file di skill. Per i criteri gestiti, li esegue solo quando cambia managed-settings.json o un file in managed-settings.d/. Applica le impostazioni gestite dal server e le modifiche alle preferenze gestite di macOS o ai criteri del registro di Windows senza eseguirli. Su WSL con wslInheritsWindowsSettings, applica anche un file di impostazioni gestite lato Windows modificato durante il proprio polling dei criteri senza eseguirli.

Il matcher filtra in base all'origine della configurazione:

Matcher Quando si attiva
user_settings Cambia ~/.claude/settings.json
project_settings Cambia .claude/settings.json
local_settings Cambia .claude/settings.local.json
policy_settings Cambia managed-settings.json o un file in managed-settings.d/
skills Cambia un file di skill in .claude/skills/

Questo esempio registra tutte le modifiche alla configurazione per il controllo di sicurezza:

{
  "hooks": {
    "ConfigChange": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/audit-config-change.sh",
            "args": []
          }
        ]
      }
    ]
  }
}

Input di ConfigChange

Oltre ai campi di input comuni, gli hook ConfigChange ricevono source e, facoltativamente, file_path. Il campo source indica quale tipo di configurazione è cambiato, e file_path fornisce il percorso del file specifico che è stato modificato.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "ConfigChange",
  "source": "project_settings",
  "file_path": "/Users/.../my-project/.claude/settings.json"
}

Controllo delle decisioni di ConfigChange

Gli hook ConfigChange possono impedire che le modifiche alla configurazione abbiano effetto. Usa il codice di uscita 2 o una decision JSON per impedire la modifica. Quando viene bloccata, le nuove impostazioni non vengono applicate alla sessione in esecuzione.

Campo Descrizione
decision "block" impedisce che la modifica alla configurazione venga applicata. Omettilo per consentire la modifica
reason Accettato ma mai mostrato
{
  "decision": "block",
  "reason": "Configuration changes to project settings require admin approval"
}

Le modifiche policy_settings non possono essere bloccate. Gli hook si attivano comunque per le origini policy_settings quando cambia un file di impostazioni gestite sulla macchina, quindi puoi usarli per registrare queste modifiche, ma qualsiasi decisione di blocco viene ignorata. Ciò garantisce che le impostazioni gestite dall'azienda abbiano sempre effetto. Claude Code non esegue gli hook ConfigChange quando le impostazioni gestite dal server arrivano o vengono aggiornate.

Claude Code agisce sulla decisione di blocco dall'output JSON di un hook ConfigChange e scarta systemMessage e continue. Una modifica bloccata non mostra alcun messaggio né a te né a Claude, sia che tu blocchi con reason sia con stderr e uscita 2. Claude Code scrive solo una riga nel log di debug.

CwdChanged

Viene eseguito quando un comando della shell nella conversazione principale cambia la directory di lavoro, ad esempio quando Claude esegue un comando cd. Usalo per reagire ai cambi di directory: ricaricare le variabili d'ambiente, attivare toolchain specifiche del progetto o eseguire automaticamente script di configurazione. Si abbina a FileChanged per strumenti come direnv che gestiscono l'ambiente per directory.

Gli hook CwdChanged hanno accesso a CLAUDE_ENV_FILE. Le variabili scritte in quel file persistono nei comandi Bash successivi fino al successivo evento CwdChanged, quando Claude Code le cancella.

CwdChanged non supporta i matcher e si attiva a ogni occorrenza.

Input di CwdChanged

Oltre ai campi di input comuni, gli hook CwdChanged ricevono old_cwd e new_cwd.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project/src",
  "hook_event_name": "CwdChanged",
  "old_cwd": "/Users/my-project",
  "new_cwd": "/Users/my-project/src"
}

Output di CwdChanged

Oltre ai campi di output JSON disponibili per tutti gli hook, gli hook CwdChanged possono restituire watchPaths per impostare dinamicamente quali percorsi di file vengono monitorati da FileChanged:

Campo Descrizione
watchPaths Array di percorsi assoluti. Sostituisce l'attuale elenco di monitoraggio dinamico. I percorsi della configurazione del tuo matcher vengono sempre monitorati. Restituire un array vuoto cancella l'elenco dinamico, il che è tipico quando si entra in una nuova directory

Gli hook CwdChanged non hanno controllo delle decisioni. Non possono bloccare il cambio di directory.

Claude Code legge watchPaths e systemMessage dal loro output JSON e scarta continue. Nelle sessioni interattive, mostra il systemMessage come breve notifica nel terminale. Il messaggio non raggiunge il flusso di messaggi dell'SDK.

DirectoryAdded

Viene eseguito dopo che aggiungi una directory di lavoro a sessione in corso con il comando /add-dir, oppure dopo che un client SDK ne aggiunge una con la richiesta di controllo register_repo_root. Usalo per preparare un repository appena aggiunto, ad esempio installandone le dipendenze.

Claude Code non attiva questo evento quando:

  • Passi una directory con il flag di avvio --add-dir; SessionStart copre quelle directory
  • Aggiungi una directory nella scheda Workspace di /permissions
  • Aggiungi una directory che è già una directory di lavoro o si trova al suo interno

Claude Code attiva DirectoryAdded dopo aver aggiornato lo stato della sandbox e dei permessi, quindi gli strumenti in sandbox vedono già la nuova directory quando il tuo hook viene eseguito. I comandi degli hook stessi vengono eseguiti senza sandbox.

Claude Code non attende l'hook: l'aggiunta viene completata immediatamente e l'hook viene eseguito in background con il timeout predefinito di 600 secondi.

Il matcher filtra in base a come è stata aggiunta la directory:

Matcher Quando si attiva
slash_command Aggiungi una directory con /add-dir
register_repo_root Un client SDK aggiunge una directory con la richiesta di controllo register_repo_root

Input di DirectoryAdded

Oltre ai campi di input comuni, gli hook DirectoryAdded ricevono directory e source.

Campo Descrizione
directory Percorso assoluto della directory che è stata aggiunta
source Come è stata aggiunta la directory, "slash_command" per /add-dir o "register_repo_root" per la richiesta di controllo dell'SDK
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "DirectoryAdded",
  "directory": "/Users/my-other-repo",
  "source": "slash_command"
}

Gli hook DirectoryAdded non hanno controllo delle decisioni. Non possono bloccare l'aggiunta, che è già stata completata quando l'hook viene eseguito. Claude Code scarta il campo continue dal loro output JSON e gestisce il resto in modo diverso in base all'origine:

  • slash_command: Claude Code consegna il systemMessage dell'hook a Claude come contesto al turno successivo della conversazione, anziché mostrarlo a te. Nella trascrizione appare il conteggio degli hook falliti. L'output completo degli errori va nel log di debug
  • register_repo_root: Claude Code scrive l'output di systemMessage e l'output degli errori solo nel log di debug

FileChanged

Viene eseguito quando un file monitorato cambia su disco. Claude Code rileva le modifiche con un watcher del filesystem, non ispezionando le chiamate agli strumenti, quindi esegue l'hook indipendentemente da cosa abbia modificato il file: una chiamata allo strumento Edit o Write, uno script che Claude esegue con Bash o un processo completamente esterno a Claude Code. Un uso comune è ricaricare le variabili d'ambiente quando cambiano i file di configurazione del progetto.

Il matcher per questo evento ha due ruoli:

  • Costruire l'elenco di monitoraggio: il valore viene suddiviso su | e ogni segmento viene registrato come nome di file letterale nella directory di lavoro, quindi ".envrc|.env" monitora esattamente quei due file. I pattern regex non sono utili qui: un valore come ^\.env monitorerebbe un file chiamato letteralmente ^\.env.
  • Filtrare quali hook vengono eseguiti: quando un file monitorato cambia, lo stesso valore filtra quali gruppi di hook vengono eseguiti usando le regole standard dei matcher sul nome base del file modificato.

Questo esempio normalizza i fine riga in data.csv dopo qualsiasi modifica, inclusa la riscrittura del file da parte di un comando Bash o di uno script esterno:

{
  "hooks": {
    "FileChanged": [
      {
        "matcher": "data.csv",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/normalize-line-endings.sh"
          }
        ]
      }
    ]
  }
}

L'hook legge il percorso assoluto del file modificato dal campo file_path dell'input JSON su stdin. Il suo controllo con grep verifica la stessa cosa che perl rimuove, un CR alla fine di una riga, quindi l'esecuzione successiva a una normalizzazione termina senza toccare il file. Un controllo meno rigoroso entra in un ciclo infinito, perché perl -i riscrive il file anche quando non sostituisce nulla e Claude Code esegue di nuovo l'hook dopo ogni riscrittura. Salva questo script in /path/to/normalize-line-endings.sh e rendilo eseguibile:

#!/bin/bash
FILE=$(jq -r .file_path)
if grep -q $'\r$' "$FILE"; then
  perl -pi -e 's/\r$//' "$FILE"
fi

Per verificare che l'hook funzioni, chiedi a Claude di aggiungere una riga CRLF a data.csv con un comando Bash. Claude Code esegue l'hook e il file finisce con fine riga LF.

Per monitorare file che non puoi nominare in anticipo, restituisci watchPaths da un hook per aggiornare dinamicamente l'elenco di monitoraggio. Claude Code avvia il watcher solo quando qualcosa nomina un file da monitorare, quindi inizializza l'elenco con un gruppo FileChanged il cui matcher nomina almeno un file, oppure con un hook SessionStart o CwdChanged che restituisce watchPaths. Il matcher filtra comunque quali gruppi di hook vengono eseguiti quando un file monitorato cambia, quindi assegna al gruppo che gestisce i percorsi dinamici un matcher omesso, che corrisponde a ogni file monitorato e non aggiunge nulla all'elenco di monitoraggio. Anche un matcher "*" corrisponde a ogni file, ma Claude Code lo registra nell'elenco di monitoraggio come qualsiasi altro valore, come un file letterale chiamato *.

Gli hook FileChanged hanno accesso a CLAUDE_ENV_FILE. Le variabili scritte in quel file persistono nei comandi Bash successivi fino al successivo evento CwdChanged, quando Claude Code le cancella.

Input di FileChanged

Oltre ai campi di input comuni, gli hook FileChanged ricevono file_path ed event.

Campo Descrizione
file_path Percorso assoluto del file che è cambiato
event Cosa è successo: "change" per un file modificato, "add" per un file creato o "unlink" per un file eliminato
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "FileChanged",
  "file_path": "/Users/my-project/.envrc",
  "event": "change"
}

Output di FileChanged

Oltre ai campi di output JSON disponibili per tutti gli hook, gli hook FileChanged possono restituire watchPaths per aggiornare dinamicamente quali percorsi di file vengono monitorati:

Campo Descrizione
watchPaths Array di percorsi assoluti. Sostituisce l'attuale elenco di monitoraggio dinamico. I percorsi della configurazione del tuo matcher vengono sempre monitorati. Usalo quando il tuo script di hook scopre file aggiuntivi da monitorare in base al file modificato

Gli hook FileChanged non hanno controllo delle decisioni. Non possono impedire che la modifica del file avvenga.

Claude Code legge watchPaths e systemMessage dal loro output JSON e scarta continue. Nelle sessioni interattive, mostra il systemMessage come breve notifica nel terminale. Il messaggio non raggiunge il flusso di messaggi dell'SDK.

WorktreeCreate

Viene eseguito quando viene creato un worktree, sia da claude --worktree, sia da un subagent che usa isolation: "worktree", sia per una sessione in background che Claude Code isola nel proprio worktree. Per impostazione predefinita Claude Code crea la copia di lavoro isolata con git worktree. Configurare un hook WorktreeCreate sostituisce questo comportamento git predefinito, permettendoti di usare un sistema di controllo versione diverso come SVN, Perforce o Mercurial.

Poiché l'hook sostituisce interamente il comportamento predefinito, .worktreeinclude non viene elaborato. Se devi copiare file di configurazione locali come .env nel nuovo worktree, fallo all'interno del tuo script hook.

L'hook deve restituire il percorso della directory del worktree creato. Claude Code usa questo percorso come directory di lavoro per la sessione isolata. Consulta Output di WorktreeCreate per sapere come ciascun tipo di hook restituisce il percorso.

Claude Code agisce in base al successo dell'hook e al percorso restituito, e scarta systemMessage e continue.

Questo esempio crea una copia di lavoro SVN e stampa il percorso che Claude Code dovrà usare. Sostituisci l'URL del repository con il tuo:

{
  "hooks": {
    "WorktreeCreate": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'NAME=$(jq -r .name); DIR=\"$HOME/.claude/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
          }
        ]
      }
    ]
  }
}

L'hook legge il name del worktree dall'input JSON su stdin, esegue il checkout di una copia nuova in una nuova directory e stampa il percorso della directory. L'echo sull'ultima riga è ciò che Claude Code legge come percorso del worktree. Reindirizza qualsiasi altro output su stderr in modo che non interferisca con il percorso.

Input di WorktreeCreate

Oltre ai campi di input comuni, gli hook WorktreeCreate ricevono il campo name. Si tratta di un identificatore slug per il nuovo worktree, specificato dall'utente o generato automaticamente, ad esempio bold-oak-a3f2.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "WorktreeCreate",
  "name": "feature-auth"
}

Output di WorktreeCreate

Gli hook WorktreeCreate non usano il modello decisionale standard allow/block. Al contrario, è il successo o il fallimento dell'hook a determinare l'esito. L'hook deve restituire il percorso della directory del worktree creato:

  • Hook di comando (type: "command"): stampa il percorso come ultima riga non vuota di stdout. Claude Code rimuove i codici di escape ANSI prima di leggere quella riga, quindi i banner di avvio della shell stampati prima del tuo echo vengono ignorati. Reindirizza qualsiasi altro output dell'hook su stderr.
  • Hook HTTP (type: "http"): restituisci { "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path" } } nel corpo della risposta.

Se l'hook fallisce o non produce alcun percorso, la creazione del worktree fallisce con un errore.

Claude Code risolve un percorso relativo rispetto alla directory in cui è stato eseguito l'hook, riducendo gli eventuali segmenti . o .. al suo interno. Se il percorso risultante non è una directory in cui Claude Code può entrare, la sessione stampa un errore che indica il percorso ed esce con codice 1.

Claude Code rifiuta un percorso assoluto che contiene segmenti . o .., e qualsiasi percorso che passa attraverso un collegamento simbolico sotto la radice del repository, perché un collegamento simbolico sottoposto a commit nel repository potrebbe reindirizzare il worktree al di fuori di esso. L'errore indica il componente rifiutato. Restituisci un percorso normalizzato che non passi attraverso un collegamento simbolico all'interno del repository. Prima della v2.1.216, la creazione del worktree seguiva il percorso dell'hook senza questo controllo.

WorktreeRemove

Viene eseguito quando Claude Code ripulisce un worktree creato dal tuo hook WorktreeCreate. L'evento si attiva quando:

  • Esci da una sessione worktree interattiva e scegli di rimuovere il worktree quando Claude Code te lo chiede
  • Esci da una sessione worktree interattiva a cui non hai dato un nome, Claude Code non trova file modificati o non tracciati e rimuove il worktree senza chiedertelo
  • Elimini una sessione in background che viene eseguita nel worktree

Claude Code usa git per cercare file modificati o non tracciati, quindi non ne trova nessuno in un worktree che non è un checkout git né si trova all'interno di uno, anche quando la directory contiene lavoro non sottoposto a commit. Controlla la presenza di tale lavoro nel tuo hook WorktreeRemove prima che elimini qualsiasi cosa.

Per i worktree basati su git, Claude Code gestisce la pulizia automaticamente con git worktree remove. Se hai configurato un hook WorktreeCreate, abbinalo a un hook WorktreeRemove per controllare la pulizia dei worktree che crea:

  • Nessun hook WorktreeRemove: quando Claude Code rimuove il worktree mentre esci da una sessione worktree, ripiega su git worktree remove --force sul percorso restituito dal tuo hook WorktreeCreate, quindi un worktree riconosciuto da git viene rimosso. Un worktree che git non riconosce, ad esempio uno creato dal tuo hook con un sistema di controllo versione non git, rimane su disco. Per sapere cosa comporta l'eliminazione di una sessione in background per un worktree creato da un hook, consulta le regole di eliminazione della vista agente.
  • L'hook esce con 0: il worktree viene considerato rimosso. Claude Code non legge nient'altro dall'hook, quindi assicurati che il tuo hook abbia eliminato la directory.
  • L'hook esce con un codice diverso da zero: la rimozione fallisce se la directory in worktree_path esiste ancora in seguito, e il worktree rimane su disco senza fallback git. Un hook che ha eliminato la directory prima di uscire con un codice diverso da zero viene considerato come rimosso. Per sapere come viene segnalato il fallimento, consulta Input di WorktreeRemove.

Claude Code non elimina mai un branch appartenente a un worktree creato da un hook, perché conosce solo il percorso restituito dal tuo hook WorktreeCreate. Se il tuo hook WorktreeCreate crea un branch, eliminalo nel tuo hook WorktreeRemove.

Claude Code scarta i campi di output JSON di un hook WorktreeRemove, come systemMessage e continue.

Per l'eliminazione di una sessione in background, Claude Code verifica il percorso del worktree memorizzato prima di eseguire l'hook e rifiuta un percorso che è un collegamento simbolico o che ne attraversa uno sotto la radice del repository. L'hook viene eseguito per un worktree che contiene ancora file solo quando confermi l'eliminazione nella vista agente; per un worktree di questo tipo, claude rm mantiene invece la sessione e il worktree. Prima della v2.1.216, l'hook veniva eseguito sul percorso memorizzato senza questi controlli.

Claude Code passa il percorso restituito da WorktreeCreate come worktree_path nell'input dell'hook. Questo esempio legge quel percorso e rimuove la directory:

{
  "hooks": {
    "WorktreeRemove": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'jq -r .worktree_path | xargs rm -rf'"
          }
        ]
      }
    ]
  }
}

Input di WorktreeRemove

Oltre ai campi di input comuni, gli hook WorktreeRemove ricevono il campo worktree_path, che è il percorso assoluto del worktree da rimuovere.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "WorktreeRemove",
  "worktree_path": "/Users/.../my-project/.claude/worktrees/feature-auth"
}

Il codice di uscita di un hook WorktreeRemove decide l'esito. Quando un hook esce con un codice diverso da zero e la directory in worktree_path esiste ancora in seguito, la rimozione fallisce:

  • Il worktree rimane su disco, e il comando e lo stderr dell'hook finiscono nel log di debug.
  • Se stavi eliminando una sessione in background, anche la sessione rimane. Il messaggio di rifiuto nella vista agente riporta come è terminato l'hook, ad esempio exited 1, cita l'inizio del suo stderr e indica se eliminare di nuovo la sessione rimuove comunque la directory.

PreCompact

Viene eseguito prima che Claude Code stia per eseguire un'operazione di compattazione.

Il valore del matcher indica se la compattazione è stata attivata manualmente o automaticamente:

Matcher Quando si attiva
manual /compact
auto Compattazione automatica quando la conversazione raggiunge la finestra di compattazione automatica

Esci con codice 2 per bloccare la compattazione. Per un /compact manuale, il messaggio stderr viene mostrato all'utente. Puoi anche bloccare restituendo JSON con "decision": "block".

Bloccare la compattazione automatica ha effetti diversi a seconda di quando si attiva. Se la compattazione è stata attivata in modo proattivo prima del limite di contesto, Claude Code la salta e la conversazione continua senza compattazione. Se la compattazione è stata attivata per recuperare da un errore di limite di contesto già restituito dall'API, l'errore sottostante emerge e la richiesta corrente fallisce.

Claude Code scarta i campi systemMessage e continue di un hook PreCompact.

Input di PreCompact

Oltre ai campi di input comuni, gli hook PreCompact ricevono trigger e custom_instructions. Per manual, custom_instructions contiene ciò che l'utente passa a /compact ed è null quando non passa nulla. Per auto, custom_instructions è null.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PreCompact",
  "trigger": "manual",
  "custom_instructions": null
}

PostCompact

Viene eseguito dopo che Claude Code ha completato un'operazione di compattazione. Usa questo evento per reagire al nuovo stato compattato, ad esempio per registrare il riepilogo generato o aggiornare uno stato esterno. Claude Code scarta i campi systemMessage e continue di un hook PostCompact.

Si applicano gli stessi valori del matcher di PreCompact:

Matcher Quando si attiva
manual Dopo /compact
auto Dopo la compattazione automatica quando la conversazione raggiunge la finestra di compattazione automatica

Input di PostCompact

Oltre ai campi di input comuni, gli hook PostCompact ricevono trigger e compact_summary. Il campo compact_summary contiene il riepilogo della conversazione generato dall'operazione di compattazione.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PostCompact",
  "trigger": "manual",
  "compact_summary": "Summary of the compacted conversation..."
}

Gli hook PostCompact non hanno controllo decisionale. Non possono influire sul risultato della compattazione, ma possono eseguire attività successive.

PreModelSwitch

Viene eseguito prima che Claude Code applichi un cambio di modello richiesto da te o da un client. Usalo per bloccare un cambio, richiedere una conferma o mostrare quanto costerà il cambio prima che avvenga.

PreModelSwitch richiede Claude Code v2.1.251 o successiva. Claude Code lo esegue per queste richieste:

  • /model <name> e il selettore di /model
  • Il selettore di modello Option+P o Alt+P
  • L'impostazione Model in /config
  • L'attivazione della modalità veloce quando questa cambia il modello della sessione
  • Una richiesta set_model, o un cambio di modello in una richiesta apply_flag_settings, da un host dell'Agent SDK o da Remote Control

Claude Code non esegue gli hook PreModelSwitch per i cambi che effettua autonomamente, come un fallback automatico del modello o il ripristino del modello quando riprendi una sessione. Queste modifiche raggiungono solo PostModelSwitch.

Claude Code confronta il matcher con il nome canonico del modello a cui la sessione sta passando, ignorando un eventuale suffisso [1m]. Un alias come opus, un ID di modello con data e un ID specifico del provider come un ID di modello Amazon Bedrock corrispondono tutti all'unico nome canonico in cui si risolvono, quindi claude-opus-5 copre ogni grafia di Opus 5.

Quando Claude Code non riesce a determinare un nome canonico per la destinazione, ad esempio un ID di modello personalizzato noto solo al tuo gateway LLM, esegue ogni hook PreModelSwitch indipendentemente dal matcher. Un hook che blocca dovrebbe quindi controllare to_model dal proprio input anziché affidarsi solo al matcher.

Scrivi il matcher come nome esatto, come elenco separato da | come claude-opus-4-6|claude-opus-5, o come espressione regolare come .*opus.*. Questo esempio usa un matcher con nome esatto e controlla anche to_model dall'input dell'hook, quindi rifiuta un cambio a Opus 4.6 uscendo con codice 2 e lascia passare qualsiasi altra destinazione:

Il comando controlla to_model con jq:

{
"hooks": {
"PreModelSwitch": [
{
"matcher": "claude-opus-4-6",
"hooks": [
{
"type": "command",
"command": "jq -e '.to_model | test(\"opus-4-6\")' > /dev/null && { echo 'Opus 4.6 is retired for this project. Use a newer model.' >&2; exit 2; }; exit 0"
}
]
}
]
}
}

Per verificare che l'hook funzioni, esegui /model claude-opus-4-6 da una sessione che usa un modello diverso. Claude Code mantiene il modello corrente e segnala che un hook PreModelSwitch ha bloccato il cambio, con il tuo messaggio come motivo.

Input di PreModelSwitch

Oltre ai campi di input comuni, gli hook PreModelSwitch ricevono i campi in questa tabella. Gli ultimi cinque descrivono quanto costa reinviare la conversazione al nuovo modello, così un hook può mostrare quella cifra prima che il cambio avvenga.

Campo Tipo Descrizione
from_model string ID del modello da cui parte il cambio
to_model string ID del modello a cui porta il cambio. Il matcher viene confrontato con il nome canonico di questo modello
requested_model string o null Il modello indicato dalla richiesta: un alias come opus, un ID di modello completo, oppure null quando la richiesta era per il modello predefinito
source string Da dove proviene la richiesta: "command" per /model <name>, l'impostazione Model in /config o l'attivazione della modalità veloce; "picker" per un selettore di modello; "sdk" per una richiesta set_model, o un cambio di modello in una richiesta apply_flag_settings, da un host dell'Agent SDK o da Remote Control
context_tokens number Token che la richiesta successiva reinvia come prompt: i token di input, lettura dalla cache, creazione della cache e output dell'ultima risposta nella conversazione principale, combinati. 0 prima della prima risposta
prompt_cache_warm boolean Indica se la cache del prompt del modello corrente è probabilmente ancora calda, il che significa che il cambio la perde
cache_ttl string Durata della cache del prompt che Claude Code richiede per questa sessione: "5m" o "1h"
estimated_cache_write_usd number Costo stimato in dollari USA della scrittura di context_tokens nella cache del prompt su to_model alla tariffa di cache_ttl, esclusa la risposta successiva. Il server potrebbe non dover rimemorizzare nella cache l'intero contesto, quindi consideralo una stima
pricing string Come Claude Code ha calcolato il prezzo di estimated_cache_write_usd: "configured" alle tariffe della tua organizzazione quando le ha configurate, "catalog" al prezzo di listino, oppure "default" quando to_model non ha un prezzo noto e Claude Code ha ipotizzato una tariffa predefinita

Questo esempio mostra l'input per /model opus in una sessione che usa Sonnet 5:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PreModelSwitch",
  "from_model": "claude-sonnet-5",
  "to_model": "claude-opus-5",
  "requested_model": "opus",
  "source": "command",
  "context_tokens": 182340,
  "prompt_cache_warm": true,
  "cache_ttl": "5m",
  "estimated_cache_write_usd": 1.1396,
  "pricing": "catalog"
}

Controllo decisionale di PreModelSwitch

Gli hook PreModelSwitch possono annullare il cambio, chiedere all'utente di confermarlo o lasciarlo procedere. Il codice di uscita 2 o un decision: "block" di primo livello annulla il cambio.

Per un controllo più preciso, restituisci permissionDecision e permissionDecisionReason in un oggetto hookSpecificOutput, come per PreToolUse. PreModelSwitch accetta "allow", "deny" e "ask". Non accetta "defer", updatedInput né additionalContext. La tabella seguente descrive entrambi i campi:

Campo Descrizione
permissionDecision "allow" procede e salta la conferma che Claude Code mostra mentre la cache del prompt è calda. "deny" annulla il cambio. "ask" chiede all'utente di confermarlo
permissionDecisionReason Per "deny", viene mostrato all'utente come motivo del blocco del cambio, oppure restituito come errore per una richiesta set_model. Per "ask", viene mostrato nella richiesta di conferma. Ignorato per "allow"

Solo /model in una sessione interattiva può mostrare la richiesta "ask". Su ogni altra superficie, inclusa la modalità non interattiva con il flag -p, /config e le richieste set_model, Claude Code tratta "ask" come un rifiuto.

Questo esempio chiede all'utente di confermare e cita il numero di token da context_tokens:

{
  "hookSpecificOutput": {
    "hookEventName": "PreModelSwitch",
    "permissionDecision": "ask",
    "permissionDecisionReason": "Switching now re-sends about 180k tokens to the new model. Continue?"
  }
}

Quando più hook PreModelSwitch restituiscono decisioni diverse, la precedenza è deny > ask > allow.

Claude Code mostra all'utente qualsiasi systemMessage restituito dal tuo hook indipendentemente dalla decisione, quindi un hook che riporta i costi può restituire {"systemMessage": "..."} e uscire con 0.

Un hook PreModelSwitch che non risponde prima del suo timeout blocca il cambio. Per sapere cosa fa un timeout negli altri eventi, consulta Timeout. Il timeout predefinito per questo evento è di 30 secondi. PreModelSwitch esegue solo hook command, http e mcp_tool, quindi i valori predefiniti di prompt e agent non si applicano.

Un hook che esce con un codice diverso da 0 o 2 e non stampa alcuna decisione JSON è un errore non bloccante, come descritto in Altri codici di uscita.

PostModelSwitch

Viene eseguito dopo che il modello della sessione è cambiato. Usalo per dare a Claude indicazioni specifiche per il modello senza modificare ogni CLAUDE.md, ad esempio un'istruzione a livello di organizzazione che si applica su determinati modelli.

PostModelSwitch richiede Claude Code v2.1.251 o successiva. Non può bloccare, perché il modello è già cambiato. Claude Code esegue gli hook PostModelSwitch dopo uno qualsiasi di questi cambiamenti:

  • Un cambio richiesto da te o da un client
  • Un fallback automatico del modello, che cambia il modello della sessione
  • Un'impostazione come opusplan che entra o esce dal plan mode
  • Claude Code che ripristina il modello quando riprendi una sessione

Claude Code non esegue gli hook PostModelSwitch quando un modello di una catena di modelli di fallback gestisce un turno, perché quella sostituzione dura un solo turno e lascia invariato il modello della sessione.

Il matcher segue le stesse regole di PreModelSwitch: Claude Code lo confronta con il nome canonico del modello a cui la sessione è passata.

Questo esempio aggiunge indicazioni ogni volta che il modello della sessione passa a un qualsiasi modello Opus:

{
  "hooks": {
    "PostModelSwitch": [
      {
        "matcher": ".*opus.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'On Opus, delegate implementation work to subagents and keep this conversation for planning and review.'"
          }
        ]
      }
    ]
  }
}

Per verificare che l'hook funzioni, passa a un modello Opus da una sessione che usa un modello diverso, ad esempio esegui /model opus da una sessione Sonnet, quindi chiedi a Claude quali indicazioni ha sul modello corrente.

Input di PostModelSwitch

Gli hook PostModelSwitch ricevono gli stessi campi di PreModelSwitch, con hook_event_name impostato su "PostModelSwitch" e due ulteriori valori di source: "auto" per un fallback automatico o un'altra modifica che Claude Code ha effettuato autonomamente, e "resume" per il modello ripristinato quando riprendi una sessione.

requested_model è null quando source è "auto". Quando source è "resume", è l'impostazione del modello salvata che Claude Code ha ripristinato.

Controllo decisionale di PostModelSwitch

Claude Code prende lo stdout in testo semplice del tuo hook all'uscita con 0, oppure additionalContext dall'output JSON, e lo consegna a Claude con la richiesta successiva al cambio. Oltre ai campi di output JSON disponibili per tutti gli hook, puoi restituire:

Campo Descrizione
additionalContext Stringa aggiunta al contesto di Claude con la richiesta successiva. Consulta Aggiungere contesto per Claude

Se l'hook non ha terminato entro cinque secondi dall'invio del prompt successivo, Claude Code invia quella richiesta senza l'output e lo allega invece alla richiesta seguente. Se il modello cambia più volte prima della richiesta successiva, Claude Code consegna solo l'output relativo al modello di destinazione dell'ultimo cambio.

SessionEnd

Viene eseguito quando una sessione di Claude Code termina. Utile per attività di pulizia, per registrare le statistiche della sessione o salvare lo stato della sessione. Supporta i matcher per filtrare in base al motivo di uscita.

Il campo reason nell'input dell'hook indica perché la sessione è terminata:

Motivo Descrizione
clear Sessione cancellata con il comando /clear
resume Sessione cambiata tramite /resume interattivo
logout L'utente è uscito dall'account
prompt_input_exit L'utente è uscito mentre l'input del prompt era visibile
other Altri motivi di uscita
bypass_permissions_disabled Rimosso nella v2.1.234; Claude Code non lo invia. Rimuovilo dai tuoi matcher SessionEnd

Input di SessionEnd

Oltre ai campi di input comuni, gli hook SessionEnd ricevono un campo reason che indica perché la sessione è terminata. Consulta la tabella dei motivi sopra per tutti i valori.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SessionEnd",
  "reason": "other"
}

Gli hook SessionEnd non hanno controllo decisionale. Non possono bloccare la terminazione della sessione, ma possono eseguire attività di pulizia. Claude Code scarta i loro campi di output JSON, come systemMessage.

Gli hook SessionEnd hanno un timeout predefinito di 1,5 secondi. Si applica quando esci, esegui /clear o cambi sessione con /resume interattivo. Puoi concedere più tempo a un hook in due modi:

  • timeout per hook: imposta timeout nella configurazione di quell'hook. Il budget complessivo aumenta automaticamente fino al timeout per hook più alto nei tuoi file di impostazioni, fino a 60 secondi. Se aumenti il budget in questo modo, un hook senza un proprio timeout mantiene comunque il valore predefinito. I timeout impostati sugli hook forniti dai plugin non aumentano il budget.
  • CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS: imposta questa variabile d'ambiente in millisecondi per sovrascrivere esplicitamente il budget. Il valore impostato diventa anche il timeout per ogni hook senza un proprio timeout.

Questo esempio imposta il budget a 5 secondi:

CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude

Prima della v2.1.268, CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS aumentava solo il budget complessivo, e un hook senza un proprio timeout veniva comunque annullato dopo 1,5 secondi.

Elicitation

Viene eseguito quando un server MCP richiede l'input dell'utente durante un'attività. Per impostazione predefinita, Claude Code mostra una finestra di dialogo interattiva a cui l'utente risponde. Gli hook possono intercettare questa richiesta e rispondere in modo programmatico, saltando completamente la finestra di dialogo.

Per un hook completo con la relativa voce di impostazioni e lo script, consulta Rispondere a una richiesta di modulo da uno script.

Il campo matcher viene confrontato con il nome del server MCP.

Input di Elicitation

Oltre ai campi di input comuni, gli hook Elicitation ricevono mcp_server_name, message e i campi opzionali mode, url, elicitation_id e requested_schema.

Per l'elicitation in modalità modulo, il caso più comune:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Elicitation",
  "mcp_server_name": "my-mcp-server",
  "message": "Please provide your credentials",
  "mode": "form",
  "requested_schema": {
    "type": "object",
    "properties": {
      "username": { "type": "string", "title": "Username" }
    }
  }
}

Per l'elicitation in modalità URL, usata per l'autenticazione basata su browser:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Elicitation",
  "mcp_server_name": "my-mcp-server",
  "message": "Please authenticate",
  "mode": "url",
  "url": "https://auth.example.com/login"
}

Output di Elicitation

Un hook Elicitation può rispondere alla richiesta al posto dell'utente, rifiutarla o annullarla, oppure lasciarla alla finestra di dialogo. Per rispondere, rifiutare o annullare, esci con 0 e stampa un oggetto hookSpecificOutput con un action. Il server riceve la tua risposta e non compare alcuna finestra di dialogo. Ogni riga di questa tabella mostra cosa restituire per un esito e cosa riceve il server MCP:

Per Restituisci Il server riceve
Rispondere al posto dell'utente "action": "accept", con i valori dei campi del modulo in content accept con il tuo content
Rifiutare la richiesta "action": "decline" decline
Annullare la richiesta "action": "cancel" cancel
Lasciare la richiesta all'utente Nessun output, con codice di uscita 0 La risposta dell'utente dalla finestra di dialogo

Questo output risponde alla richiesta in modalità modulo mostrata in Input di Elicitation. Le chiavi in content sono i nomi delle proprietà dal requested_schema di quella richiesta:

{
  "hookSpecificOutput": {
    "hookEventName": "Elicitation",
    "action": "accept",
    "content": {
      "username": "alice"
    }
  }
}

Questo output rifiuta una richiesta:

{
  "hookSpecificOutput": {
    "hookEventName": "Elicitation",
    "action": "decline"
  }
}

Nella finestra di dialogo, selezionare Decline invia decline e premere Esc invia cancel, quindi restituisci quello che vuoi che il server veda.

Per una richiesta in modalità URL, un hook che restituisce accept salta la finestra di dialogo, quindi l'URL non viene mai aperto.

Claude Code scarta reason, systemMessage e continue dall'output JSON di un hook Elicitation, qualunque action tu restituisca.

Altri modi per rifiutare un'elicitation

Il tuo hook può anche rifiutare nei modi seguenti. Il server riceve lo stesso decline di "action": "decline":

  • Esce con codice 2: Claude Code ignora un hookSpecificOutput stampato dallo stesso hook
  • Stampa un "decision": "block" di primo livello: il blocco ha la precedenza su un action nello stesso output

Quando più hook corrispondono alla stessa richiesta, un rifiuto da parte di uno di essi ha la precedenza su un accept o un cancel di un altro.

Questo script rifiuta le richieste in modalità URL e lascia le richieste di modulo alla finestra di dialogo:

#!/bin/bash
if [ "$(jq -r '.mode')" = "url" ]; then
  exit 2
fi

Né l'utente né il server vedono perché il tuo hook ha rifiutato, perché Claude Code non mostra il tuo stderr né il tuo reason.

Claude Code ignorava un decision di primo livello dagli hook Elicitation ed ElicitationResult dalla v2.1.105 fino alla correzione nella v2.1.284.

Rispondere a una richiesta di modulo da uno script

Questo esempio risponde a una domanda ricorrente al posto dell'utente. Un server MCP chiamato issue-tracker chiede una chiave di progetto in un modulo, e l'hook inserisce DOCS. Lo script accetta quando project_key è l'unico campo del modulo. Per qualsiasi altra richiesta non stampa nulla, quindi compare la finestra di dialogo.

Registra un hook di comando per l'evento nel tuo file di impostazioni, con il nome del server come matcher:

{
"hooks": {
"Elicitation": [
{
"matcher": "issue-tracker",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/answer-project-key.sh",
"args": []
}
]
}
]
}
}

Salva questo script in .claude/hooks/answer-project-key.sh nel tuo progetto e rendilo eseguibile con chmod +x:

#!/bin/bash
input=$(cat)
fields=$(jq -c '.requested_schema.properties // {} | keys' <<<"$input")

if [ "$fields" = '["project_key"]' ]; then
jq -n '{hookSpecificOutput: {hookEventName: "Elicitation", action: "accept", content: {project_key: "DOCS"}}}'
fi

Per verificare che l'hook funzioni, avvia Claude Code con claude --debug e assegna a Claude un'attività che porti il server a chiedere la chiave di progetto. Non compare alcuna finestra di dialogo, e il log di debug contiene una riga che termina con Elicitation resolved by hook: {"action":"accept","content":{"project_key":"DOCS"}}.

ElicitationResult

Viene eseguito dopo che un utente risponde a un'elicitation MCP. Gli hook possono osservare, modificare o bloccare la risposta prima che venga rinviata al server MCP.

Quando un hook Elicitation risponde a una richiesta, Claude Code invia quella risposta al server senza eseguire gli hook ElicitationResult.

Il campo matcher viene confrontato con il nome del server MCP.

Input di ElicitationResult

Oltre ai campi di input comuni, gli hook ElicitationResult ricevono mcp_server_name, action e i campi opzionali mode, elicitation_id e content.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "ElicitationResult",
  "mcp_server_name": "my-mcp-server",
  "action": "accept",
  "content": { "username": "alice" },
  "mode": "form"
}

Output di ElicitationResult

Un hook ElicitationResult può lasciar passare la risposta dell'utente, modificarne i valori o bloccarla. Per modificare o bloccare la risposta, esci con 0 e stampa un oggetto hookSpecificOutput con un action. Ogni riga di questa tabella mostra cosa restituire per un esito e cosa riceve il server MCP:

Per Restituisci Il server riceve
Lasciar passare la risposta Nessun output, con codice di uscita 0 La risposta dell'utente, invariata
Modificare i valori inviati "action": "accept", con i nuovi valori in content accept con il tuo content al posto dei valori dell'utente
Bloccare la risposta "action": "decline" decline, senza i valori dell'utente
Annullare la richiesta "action": "cancel" cancel, insieme ai valori inviati dall'utente. Per non inviarli, restituisci "decline"

Questo output modifica la risposta mostrata in Input di ElicitationResult, così il server riceve alice@example.com dove l'utente ha inviato alice:

{
  "hookSpecificOutput": {
    "hookEventName": "ElicitationResult",
    "action": "accept",
    "content": {
      "username": "alice@example.com"
    }
  }
}

Il tuo content sostituisce l'intero oggetto content dell'utente, quindi includi i campi che non stai modificando. Restituisci action insieme a esso, perché Claude Code ignora un hookSpecificOutput che non ha action.

Gli hook ElicitationResult vengono eseguiti anche quando l'utente rifiuta o annulla, e il tuo action sostituisce il suo. Verifica che l'action dell'input sia accept prima di restituire accept, altrimenti il tuo hook trasforma una richiesta rifiutata in una accettata. Questo script apporta la stessa modifica quando l'utente ha accettato, mantiene gli altri campi e altrimenti non stampa nulla:

#!/bin/bash
input=$(cat)

if [ "$(jq -r '.action' <<<"$input")" = "accept" ]; then
  jq '{hookSpecificOutput: {hookEventName: "ElicitationResult", action: "accept", content: (.content + {username: (.content.username + "@example.com")})}}' <<<"$input"
fi

Questo output blocca la risposta:

{
  "hookSpecificOutput": {
    "hookEventName": "ElicitationResult",
    "action": "decline"
  }
}

Anche il codice di uscita 2 e un "decision": "block" di primo livello bloccano la risposta. Altri modi per rifiutare un'elicitation spiega quale ha effetto quando un hook li combina, cosa vede l'utente e quali versioni ignoravano decision.

Claude Code scarta reason, systemMessage e continue dall'output JSON di un hook ElicitationResult, qualunque action tu restituisca.

Hook basati su prompt

Oltre agli hook di comando, HTTP e MCP tool, Claude Code supporta gli hook basati su prompt (type: "prompt") che utilizzano un LLM per valutare se consentire o bloccare un'azione, e gli hook basati su agenti (type: "agent") che generano un verificatore agentico con accesso agli strumenti. Non tutti gli eventi supportano ogni tipo di hook.

Gli eventi che supportano tutti e cinque i tipi di hook (command, http, mcp_tool, prompt e agent):

  • PermissionDenied
  • PostToolBatch
  • PostToolUse
  • PostToolUseFailure
  • PreToolUse
  • Stop
  • SubagentStop
  • TaskCompleted
  • TaskCreated
  • TeammateIdle
  • UserPromptExpansion
  • UserPromptSubmit

PermissionRequest supporta gli hook command, http, mcp_tool e prompt ma non gli hook agent. Se configuri un hook agent su questo evento, Claude Code lo salta e il flusso di autorizzazione procede invariato. Per consentire o negare da un hook, restituisci l'oggetto decisione da un hook di comando o HTTP.

Gli eventi che supportano gli hook command, http e mcp_tool ma non prompt o agent:

  • ConfigChange
  • CwdChanged
  • DirectoryAdded
  • Elicitation
  • ElicitationResult
  • FileChanged
  • InstructionsLoaded
  • MessageDisplay
  • Notification
  • PostCompact
  • PostModelSwitch
  • PreCompact
  • PreModelSwitch
  • SessionEnd
  • StopFailure
  • SubagentStart
  • WorktreeCreate
  • WorktreeRemove

SessionStart e Setup supportano gli hook command e mcp_tool, e i campi degli hook MCP tool descrivono quando i loro hook mcp_tool vengono eseguiti. Non supportano gli hook http, prompt o agent.

Come funzionano gli hook basati su prompt

Invece di eseguire un comando Bash, gli hook basati su prompt:

  1. Inviano l'input del hook e il prompt a un modello Claude, per impostazione predefinita quello che Claude Code utilizza per la funzionalità in background
  2. L'LLM risponde con JSON strutturato contenente una decisione
  3. Claude Code elabora automaticamente la decisione

Configurazione del prompt hook

Impostare type su "prompt" e fornire una stringa prompt invece di un command. Utilizzare il segnaposto $ARGUMENTS per iniettare i dati di input JSON del hook nel testo del prompt.

In un hook di prompt o in un hook agente, puoi scrivere il prompt come una regola su cosa bloccare o consentire, ad esempio "Blocca qualsiasi comando Bash che legge file .env", oppure come una condizione che deve essere soddisfatta, ad esempio "Tutti i test unitari passano".

Questo hook Stop chiede all'LLM di valutare se tutti i compiti sono completi prima di consentire a Claude di terminare:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
          }
        ]
      }
    ]
  }
}
Campo Obbligatorio Descrizione
type sì Deve essere "prompt"
prompt sì Il testo del prompt da inviare all'LLM. Utilizzare $ARGUMENTS come segnaposto per l'input JSON del hook. Se $ARGUMENTS non è presente, l'input JSON viene aggiunto al prompt
model no Modello da utilizzare per la valutazione. Impostazione predefinita: il modello che Claude Code utilizza per la funzionalità in background
timeout no Timeout in secondi. Impostazione predefinita: 30
continueOnBlock no Sugli eventi a cui si applica, true reinvia un motivo ok: false a Claude e continua invece di terminare il turno. Impostazione predefinita: false. Vedere Schema di risposta per il comportamento per evento

Schema di risposta

L'LLM deve rispondere con JSON contenente:

{
  "ok": true | false,
  "reason": "Explanation for the decision",
  "impossible": true | false
}
Campo Descrizione
ok true per consentire. Per false, vedere il comportamento per evento di seguito
reason Obbligatorio quando ok è false
impossible Facoltativo. Il modello lo restituisce con ok: false quando giudica che la condizione non può mai essere soddisfatta. Su Stop e SubagentStop, Claude Code consente quindi al turno di terminare invece di reinviare il motivo. Gli hook agenti e altri eventi lo ignorano

Ciò che accade con ok: false dipende dall'evento:

  • Stop e SubagentStop: il motivo viene reinviato a Claude come sua prossima istruzione e il turno continua, a meno che la risposta non imposti anche impossible: true, nel qual caso Claude Code consente lo stop e il turno termina
  • PreToolUse: la chiamata dello strumento viene negata; per impostazione predefinita il turno termina e il motivo della negazione appare nella chat come una riga di avviso. Impostare continueOnBlock: true per reinviare il motivo a Claude come errore dello strumento in modo che possa adattarsi e continuare, equivalente a un hook di comando con permissionDecision: "deny". Prima della v2.1.210, il motivo della negazione veniva restituito a Claude come errore dello strumento e il turno continuava
  • PostToolUse: per impostazione predefinita il turno termina e il motivo appare nella chat come una riga di avviso. Impostare continueOnBlock: true per reinviare il motivo a Claude e continuare il turno invece
  • PostToolBatch, UserPromptSubmit e UserPromptExpansion: il turno termina e il motivo appare come una riga di avviso. Questi eventi terminano il turno su decision: "block" indipendentemente da continue
  • PostToolUseFailure e TaskCreated: il motivo viene restituito a Claude come errore dello strumento e il turno continua, indipendentemente da continueOnBlock
  • TaskCompleted: quando si attiva perché un'attività è contrassegnata come completata durante un turno, il motivo viene restituito a Claude come errore dello strumento e il turno continua, indipendentemente da continueOnBlock. Quando si attiva perché un compagno di squadra si ferma, si comporta come TeammateIdle e arresta il compagno di squadra per impostazione predefinita
  • TeammateIdle: per impostazione predefinita il compagno di squadra si ferma e il motivo appare come una riga di avviso. Impostare continueOnBlock: true per reinviare il motivo al compagno di squadra e mantenerlo al lavoro invece
  • PermissionRequest: ok: false non ha effetto. Per negare un'approvazione da un hook, utilizzare un hook di comando che restituisce hookSpecificOutput.decision.behavior: "deny"
  • PermissionDenied: ok: false non ha effetto perché il rifiuto è già avvenuto. L'unico output che questo evento legge è hookSpecificOutput.retry, che gli hook di prompt e agenti non possono impostare. Vengono eseguiti su questo evento, ma il loro output viene scartato. Utilizzare un hook di comando per restituire retry

Se hai bisogno di un controllo più fine su qualsiasi evento, utilizza un hook di comando con i campi per evento descritti in Controllo delle decisioni.

Controllare più condizioni prima di fermarsi

Questo hook Stop utilizza un prompt dettagliato per controllare tre condizioni prima di consentire a Claude di fermarsi. Gli hook SubagentStop utilizzano lo stesso formato per valutare se un subagent dovrebbe fermarsi. Se il modello restituisce "ok": false perché la condizione non è ancora soddisfatta, Claude continua a lavorare con il motivo fornito come sua prossima istruzione:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Hook basati su agenti

Gli hook basati su agenti (type: "agent") sono come gli hook basati su prompt ma con accesso agli strumenti multi-turno. Invece di una singola chiamata LLM, un hook agente genera un subagent che può leggere file, cercare codice e ispezionare il codebase per verificare le condizioni. Gli hook agente supportano gli stessi eventi degli hook basati su prompt, ad eccezione di PermissionRequest.

Come funzionano gli hook basati su agenti

Quando un hook agente si attiva:

  1. Claude Code genera un subagent con il prompt e l'input JSON del hook
  2. Il subagent può utilizzare strumenti come Read, Grep e Glob per investigare
  3. Dopo fino a 50 turni, il subagent restituisce una decisione strutturata { "ok": true/false }
  4. Claude Code consente l'azione se ok è true. Se ok è false, Claude Code gestisce il blocco nello stesso modo di un hook di prompt con continueOnBlock: true su quell'evento, come elencato sotto Schema di risposta

Gli hook agente sono utili quando la verifica richiede l'ispezione dei file effettivi o dell'output dei test, non solo la valutazione dei dati di input del hook da soli.

Configurazione dell'hook agente

Impostare type su "agent" e fornire una stringa prompt, utilizzando $ARGUMENTS come segnaposto per l'input JSON del hook. I campi di configurazione sono gli stessi degli hook di prompt, ad eccezione del fatto che gli hook agente hanno un timeout predefinito più lungo di 60 secondi e nessun campo continueOnBlock.

Lo schema di risposta è { "ok": true } per consentire o { "ok": false, "reason": "..." } per bloccare. Su ok: false, Claude Code gestisce un hook agente nello stesso modo in cui gestisce un hook di prompt con continueOnBlock: true sullo stesso evento; gli hook agente non hanno un campo continueOnBlock e non supportano il campo impossible dell'hook di prompt.

Questo hook Stop verifica che tutti i test unitari passino prima di consentire a Claude di finire:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

Eseguire i hook in background

Per impostazione predefinita, gli hook bloccano l'esecuzione di Claude fino al completamento. Per le attività a lunga esecuzione come distribuzioni, suite di test o chiamate API esterne, impostare "async": true per eseguire l'hook in background mentre Claude continua a lavorare. Gli hook asincroni non possono bloccare o controllare il comportamento di Claude: i campi di risposta come decision, permissionDecision e continue non hanno effetto, perché l'azione che avrebbero controllato è già stata completata.

Configurare un hook asincrono

Aggiungere "async": true alla configurazione di un command hook per eseguirlo in background senza bloccare Claude. Questo campo è disponibile solo sui hook type: "command".

Questo hook esegue uno script di test dopo ogni chiamata dello strumento Write. Claude continua a lavorare immediatamente mentre run-tests.sh viene eseguito. Quando lo script termina, l'output viene consegnato al turno di conversazione successivo:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/run-tests.sh",
            "async": true
          }
        ]
      }
    ]
  }
}

Una volta che un hook asincrono è in esecuzione in background, Claude Code non applica timeout su di esso. Claude Code continua ad applicare timeout su un hook che si esegue con asyncRewake.

Claude Code consegna i risultati di un hook asincrono solo mentre la sessione è in esecuzione:

  • In modalità non interattiva con il flag -p, Claude Code termina qualsiasi hook asincrono ancora in esecuzione al teardown e lo finalizza con esito cancelled
  • Se il lavoro del vostro hook deve sopravvivere a una sessione claude -p, avviate un processo completamente staccato da esso

Come vengono eseguiti gli hook asincroni

Quando un hook asincrono si attiva, Claude Code avvia il processo del hook e continua immediatamente senza aspettare il completamento. L'hook riceve lo stesso input JSON tramite stdin di un hook sincrono.

Dopo che il processo in background esce, Claude Code consegna i campi additionalContext e systemMessage dalla risposta JSON dell'hook a Claude al turno di conversazione successivo. A differenza di systemMessage di un hook sincrono, nessuno dei due campi viene mostrato a voi.

Claude Code convalida quella risposta JSON rispetto allo stesso schema di output degli hook sincroni e scarta qualsiasi campo il cui valore ha il tipo errato, come un systemMessage che non è una stringa, invece di consegnarlo. Eseguire con --debug per vedere un avviso che nomina ogni campo scartato. Prima della v2.1.202, l'output JSON malformato da un hook asincrono poteva causare l'arresto della sessione e l'arresto si ripeteva ogni volta che la sessione veniva ripresa.

Le notifiche di completamento degli hook asincroni sono soppresse per impostazione predefinita. Per vederle, abilitare la modalità verbose con Ctrl+O o avviare Claude Code con --verbose.

Eseguire i test dopo le modifiche ai file

Questo hook avvia una suite di test in background ogni volta che Claude scrive un file, quindi segnala i risultati a Claude quando i test terminano. Salvare questo script in .claude/hooks/run-tests-async.sh nel progetto e renderlo eseguibile con chmod +x:

#!/bin/bash
# run-tests-async.sh

# Leggere l'input del hook da stdin
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Eseguire i test solo per i file di origine
if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
  exit 0
fi

# Eseguire i test e segnalare i risultati a Claude tramite additionalContext
RESULT=$(npm test 2>&1)
EXIT_CODE=$?

if [ $EXIT_CODE -eq 0 ]; then
  MSG="Tests passed after editing $FILE_PATH"
else
  MSG="Tests failed after editing $FILE_PATH: $RESULT"
fi
jq -nc --arg msg "$MSG" '{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: $msg}}'

Quindi aggiungere questa configurazione a .claude/settings.json nella radice del progetto. Il flag async: true consente a Claude di continuare a lavorare mentre i test vengono eseguiti:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/run-tests-async.sh",
            "args": [],
            "async": true
          }
        ]
      }
    ]
  }
}

Limitazioni

Gli hook asincroni hanno vincoli aggiuntivi rispetto agli hook sincroni:

  • L'output del hook viene consegnato al turno di conversazione successivo. Se la sessione è inattiva, la risposta attende fino alla prossima interazione dell'utente. Eccezione: un hook asyncRewake che esce con il codice 2 riattiva Claude immediatamente anche quando la sessione è inattiva.
  • Ogni esecuzione crea un processo in background separato.

Considerazioni sulla sicurezza

Disclaimer

Fiducia nell'area di lavoro

Claude Code verifica la fiducia nell'area di lavoro prima di eseguire qualsiasi hook da un file di impostazioni. Ciò che conta come attendibile dipende dal tipo di sessione:

  • Sessione interattiva: Claude Code trattiene i hook da ogni file di impostazioni, incluso il vostro ~/.claude/settings.json, fino a quando non accettate la finestra di dialogo di fiducia nell'area di lavoro per la cartella, o per una directory padre la cui fiducia si estende ad essa
  • Sessione -p o SDK: Claude Code non mostra mai la finestra di dialogo e tratta la cartella come attendibile, quindi i hook sottoposti a commit nel .claude/settings.json di un repository vengono eseguiti in una cartella che non avete mai considerato attendibile

Prima di eseguire lo script claude -p su un repository che non avete scritto, rivedete i file di impostazioni .claude/, iniziate con --bare, o disattivate i hook per quella esecuzione con --settings '{"disableAllHooks": true}'. I hook nel frontmatter in un subagent di progetto seguono una regola più ristretta rispetto ai hook dei file di impostazioni. Ciò che viene eseguito prima di considerare attendibile una cartella elenca ogni tipo di contenuto del repository per tipo di sessione.

Migliori pratiche di sicurezza

Tenere presenti queste pratiche quando si scrivono i hook:

  • Convalidare e disinfettare gli input: non fidarsi mai ciecamente dei dati di input
  • Citare sempre le variabili shell: utilizzare "$VAR" non $VAR
  • Bloccare l'attraversamento del percorso: controllare .. nei percorsi dei file
  • Utilizzare percorsi assoluti: specificare percorsi completi per gli script. Nel modulo exec, utilizzare ${CLAUDE_PROJECT_DIR} e il percorso non necessita di virgolette. Nel modulo shell, racchiuderlo tra virgolette doppie
  • Saltare i file sensibili: evitare .env, .git/, chiavi, ecc.

Strumento Windows PowerShell

Su Windows, è possibile eseguire singoli hook in PowerShell impostando "shell": "powershell" su un command hook. Claude Code rileva automaticamente pwsh.exe, l'eseguibile di PowerShell 7 e versioni successive, e ricade su powershell.exe per Windows PowerShell 5.1.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "shell": "powershell",
            "command": "Write-Host 'File written'"
          }
        ]
      }
    ]
  }
}

Per fare riferimento alla directory radice del progetto da un comando in forma shell di PowerShell, scrivi ${CLAUDE_PROJECT_DIR} o $env:CLAUDE_PROJECT_DIR. Claude Code riscrive i segnaposti ${CLAUDE_PROJECT_DIR}, ${CLAUDE_PLUGIN_ROOT} e ${CLAUDE_PLUGIN_DATA} in un comando in forma shell di PowerShell nella forma ${env:NAME} di PowerShell, indipendentemente dal fatto che l'hook sia definito in settings.json, un plugin o una skill. PowerShell quindi risolve il valore dall'ambiente esportato dopo l'analisi, quindi il segnaposto funziona all'interno di stringhe tra virgolette doppie ma non all'interno di stringhe tra virgolette singole, dove PowerShell non espande mai le variabili.

Non scrivere la forma nuda $CLAUDE_PROJECT_DIR in un hook di PowerShell. PowerShell la analizza come una variabile locale non definita e la risolve in $null, il che lascia il percorso dello script senza il prefisso della directory radice del progetto. Claude Code non riscrive quella forma; invece registra un avviso nel log di debug.

L'esempio seguente mostra un hook settings.json che esegue uno script di progetto con la forma $env::

{
  "type": "command",
  "shell": "powershell",
  "command": "& \"$env:CLAUDE_PROJECT_DIR\\.claude\\hooks\\check.ps1\""
}

Debug dei hook

I dettagli dell'esecuzione dei hook vengono scritti nel file di log di debug. Avviare Claude Code con claude --debug-file <path> per scrivere il log in una posizione nota, oppure eseguire claude --debug e leggere il log in ~/.claude/debug/<session-id>.txt. Il flag --debug non stampa nel terminale.

Ad esempio, un hook PostToolUse su Write il cui comando stampa hook-ran produce voci come:

2026-07-19T02:03:24.382Z [DEBUG] Hook output does not start with {, treating as plain text
2026-07-19T02:03:24.382Z [DEBUG] "Hook PostToolUse:Write (PostToolUse) success:\nhook-ran"

Per dettagli di corrispondenza dei hook più granulari, impostare CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose per visualizzare righe di log aggiuntive come i conteggi dei matcher del hook e la corrispondenza delle query.

Per la risoluzione dei problemi comuni come i hook che non si attivano, i Stop hook che continuano a bloccare, o gli errori di configurazione, consultare Limitations and troubleshooting nella guida. Per una procedura diagnostica più ampia che copre /context, /doctor e la precedenza delle impostazioni, consultare Debug your config.