SpyBara
Go Premium

hooks.md 2026-11-11 22:59 UTC to 2026-11-12 23:02 UTC

No changes for this document in this history range.

2026
Mon 2 23:57 Tue 3 23:58 Fri 6 23:01 Sat 7 23:58 Mon 9 23:02 Tue 10 21:57 Wed 11 22:59

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.

[!TIP]

Per una guida di avvio rapido con esempi, consultare Automatizzare i flussi di lavoro con i hook.

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

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.

[tab] macOS/Linux

Il matcher si restringe alle chiamate allo strumento Bash e ai comandi Monitor, e la condizione if si restringe ulteriormente ai sottocomandi Bash che corrispondono a rm *, quindi block-rm.sh viene avviato 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; si applica il normale flusso dei permessi
fi

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

[tab] Windows (PowerShell)

Il matcher Bash|PowerShell copre lo strumento PowerShell così come Bash. Una singola regola if indica un solo strumento, quindi ogni strumento ottiene il proprio gestore: il primo si restringe ai sottocomandi Bash che corrispondono a rm *, il secondo ai comandi PowerShell che corrispondono a Remove-Item *. Entrambi eseguono lo stesso script tramite powershell.exe:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|PowerShell",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(rm *)",
            "command": "powershell.exe",
            "args": [
              "-NoProfile",
              "-ExecutionPolicy",
              "Bypass",
              "-File",
              "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"
            ]
          },
          {
            "type": "command",
            "if": "PowerShell(Remove-Item *)",
            "command": "powershell.exe",
            "args": [
              "-NoProfile",
              "-ExecutionPolicy",
              "Bypass",
              "-File",
              "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"
            ]
          }
        ]
      }
    ]
  }
}

Il flag -NoProfile salta il caricamento del profilo PowerShell in modo che l'hook si avvii rapidamente, e -ExecutionPolicy Bypass consente a PowerShell di eseguire il file di script locale.

Lo script legge l'input JSON da stdin, estrae il comando e restituisce una permissionDecision di "deny" se contiene rm -rf o Remove-Item seguito da -Recurse. Salvarlo in .claude/hooks/block-rm.ps1 nel progetto:

# .claude/hooks/block-rm.ps1
$callInput = [Console]::In.ReadToEnd() | ConvertFrom-Json
$command = $callInput.tool_input.command

if ($command -match 'rm -rf|Remove-Item.*-Recurse') {
  @{
    hookSpecificOutput = @{
      hookEventName = "PreToolUse"
      permissionDecision = "deny"
      permissionDecisionReason = "Destructive command blocked by hook"
    }
  } | ConvertTo-Json
} else {
  exit 0  # nessuna decisione; si applica il normale flusso dei permessi
}

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.

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, ad esempio "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.

[!NOTE]

Questa pagina utilizza termini specifici per ogni livello: hook event per il punto del ciclo di vita, matcher group per il filtro, e hook handler per il comando shell, endpoint HTTP, strumento MCP, prompt, o agente che viene eseguito. "Hook" da solo si riferisce alla funzione generale.

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 allo strumento Bash e, su PreToolUse e PermissionRequest, anche ai comandi Monitor; 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, auth_storage_failure, 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, plugin_notification
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"
          }
        ]
      }
    ]
  }
}

Corrispondere ai comandi Monitor

Un hook PreToolUse o PermissionRequest con un matcher Bash viene eseguito anche sui comandi shell che Claude avvia con lo strumento Monitor, ma i suoi allow e updatedInput non hanno effetto su di essi. Richiede Claude Code v2.1.314 o successivo. Anche matcher come Bash|PowerShell, ^Bash$ e Bash.* sono matcher Bash. Un hook con un matcher Bash non viene eseguito per un watch WebSocket, e un hook PostToolUse con un tale matcher non viene eseguito per Monitor.

Un hook che viene eseguito per un comando Monitor può negarlo o fare in modo che Claude Code te lo chieda. Per consentire o riscrivere il comando, passa da un matcher Bash a uno che include Monitor:

  • Matcher che include Monitor: con un matcher come Bash|Monitor o .*, o senza matcher, l'hook riceve l'input della chiamata Monitor stessa, e allow e updatedInput si applicano. In if, indica Monitor, come in Monitor(tail *), che corrisponde secondo le regole seguite da Bash(tail *). Se il suo if indica Bash, l'hook riceve comunque l'input della chiamata Monitor, ma allow e updatedInput non hanno effetto
  • Matcher Bash: l'hook riceve una chiamata Bash. In if, indica Bash, come in Bash(tail *). L'hook non viene eseguito se il suo if indica Monitor

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.

Quando un evento si attiva, i suoi hook corrispondenti vengono eseguiti in parallelo. Se più di 12 di essi sono hook prompt o agent, 12 di questi vengono eseguiti contemporaneamente e gli altri partono man mano che quelli in esecuzione terminano. Il timeout di ogni hook in attesa inizia a contare quando quell'hook parte.

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 di una 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 directory home, o la directory temporanea del sistema. Claude Code registra un avviso che nomina la directory di fallback nel debug log. Per una sessione worktree che avvii dall'app desktop, vedi Cosa condividono i worktree con il checkout principale.

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/**)".

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 Se l'azione che ha attivato l'hook procede quando l'hook fallisce: "continue" o "block". Vedi Blocca l'azione quando un hook fallisce per "block" e per il caso in cui il campo non sia impostato. 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.

[!NOTE]

Su Windows, exec form richiede che command si risolva in un vero eseguibile come .exe. Gli shim .cmd e .bat che npm, npx, eslint, e altri tool installano in node_modules/.bin non sono eseguibili e non possono essere generati senza una shell. Per eseguirli in exec form, invoca lo script sottostante con node direttamente, ad esempio "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/node_modules/eslint/bin/eslint.js"]. Il pattern node più script-path funziona su ogni piattaforma perché node.exe è un vero binario. Per eseguire uno shim .cmd o .bat per nome, utilizza shell form.

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.*}.

[!NOTE]

In exec form, command è solo il nome o il percorso dell'eseguibile. Se command è un nome nudo senza separatore di percorso e contiene spazi insieme a args, Claude Code registra un avviso perché la generazione fallirà: non c'è un eseguibile denominato node script.js. Sposta i token extra in args. I percorsi assoluti con spazi, come C:\Program Files\nodejs\node.exe, sono un singolo eseguibile valido e non attivano l'avviso.

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.

Se un errore dell'hook contiene uno di questi messaggi, Claude Code non è riuscito a leggere la risposta dello strumento, quindi l'hook non ha dato alcun verdetto:

MCP tool "security_scan" on server "my_server" gave no answer that could be read, because a message from the server was too large, so this hook gave no verdict. If this keeps happening, the tool must send a smaller answer.
MCP tool "security_scan" on server "my_server" answered without its text, which happens when a result is too large, so this hook gave no verdict. If this keeps happening, the tool must send a smaller answer.

Su PreToolUse, Claude Code rifiuta la chiamata allo strumento che l'hook stava controllando. Su altri eventi come UserPromptSubmit, PostToolUse e Stop, l'errore non è bloccante.

Se l'errore continua a comparire, fai restituire meno dati allo strumento, oppure modifica tool o input dell'hook in modo che la risposta sia più piccola.

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.

[!NOTE]

I worktree sono diversi. Se Claude entra in un worktree durante la sessione, Claude Code mantiene ${CLAUDE_PROJECT_DIR} dove era e passa il percorso del worktree ai tuoi hook in un modo diverso:

  • ${CLAUDE_PROJECT_DIR} rimane fermo: punta ancora alla radice del progetto dove è iniziata la sessione, quindi un comando come ${CLAUDE_PROJECT_DIR}/.claude/hooks/check-style.sh esegue ancora lo script nel checkout principale.
  • cwd segue Claude: il campo cwd nell'input JSON dell'hook è la radice del worktree dopo che Claude entra in un worktree, e la nuova directory dopo che Claude esegue cd. Leggilo quando un hook ha bisogno di sapere quale directory Claude sta utilizzando.

Per una sessione worktree che avvii dall'app desktop, vedi Cosa condividono i worktree con il checkout principale per sapere dove punta ${CLAUDE_PROJECT_DIR}.

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

[tab] Project scripts

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": []
          }
        ]
      }
    ]
  }
}

[tab] Plugin scripts

Definisci gli hook del plugin in hooks/hooks.json con un campo description opzionale di livello superiore. Quando un plugin è abilitato, i suoi hook si uniscono ai tuoi hook utente e progetto.

Questo esempio esegue uno script di formattazione raggruppato con il plugin:

{
  "description": "Automatic code formatting",
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
            "args": [],
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Vedi il riferimento dei componenti del plugin per i dettagli sulla creazione degli hook del plugin.

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 del workspace 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 subagent elenca quali ambiti sono esenti.

Limiti sugli hook delle skill

Quando gli hook del frontmatter di una skill non si attivano dopo che tu o Claude avete invocato la skill, esegui /hooks in quella sessione. Se li elenca, consulta Hook non attivato. Se non ne elenca nessuno, la skill potrebbe essere soggetta a un blocco solo plugin o aver superato un limite di hook, e viene eseguita senza nessuno dei suoi hook del frontmatter. Verifica ciascun caso:

  • Blocco solo plugin: l'amministratore della tua organizzazione può limitare gli hook ai plugin e alle impostazioni gestite con l'impostazione gestita strictPluginOnlyCustomization. Una skill mantiene i suoi hook del frontmatter sotto quel blocco solo se è una skill di un plugin, una skill inclusa, o una skill nella directory delle impostazioni gestite. Nemmeno gli hook nei tuoi file di impostazioni vengono eseguiti.
    • Verifica: digita /status in una sessione di Claude Code. Se la riga Setting sources include Enterprise managed settings, chiedi al tuo amministratore se strictPluginOnlyCustomization blocca gli hook.
    • Soluzione: sposta la skill in un plugin, perché le skill dei plugin mantengono i loro hook del frontmatter. Elenca il plugin in un marketplace, poi installalo da lì. Se Claude Code rifiuta il marketplace con is blocked by enterprise policy, chiedi al tuo amministratore di consentire quel marketplace.
  • Limite di hook: in Claude Code v2.1.304 o successivo, ogni fonte di skill ha il proprio limite per sessione. Il tuo progetto, la tua cartella di skill personali e ciascun plugin contano come fonti separate. Le skill di una stessa fonte possono registrare al massimo 1.024 hook. I matcher di quegli hook possono totalizzare al massimo 16.384 caratteri. Un matcher con più hook sotto di sé conta una volta per ciascun hook.
    • Verifica: ripeti la sessione in cui l'hook non si è attivato, con il log di debug attivo:
      1. Avvia una nuova sessione dalla tua shell con claude --debug. Se non avvii le sessioni da una shell, digita invece prima /debug nella nuova sessione.
      2. Invoca le stesse skill nello stesso ordine, comprese quelle invocate da Claude, perché entrambi i totali si accumulano skill dopo skill. Chiedi a Claude di invocare qualsiasi skill che non puoi digitare come comando.
      3. Cerca registers none of its hooks nel debug log. Una riga che contiene quel testo indica la skill che ha superato il limite.
    • Soluzione: dichiara meno hook o matcher più brevi in quella skill o nelle altre skill della stessa fonte, poi avvia una nuova sessione. Se non puoi modificare quelle skill, chiedi quella modifica al loro autore. Fino ad allora, avvia una nuova sessione e invoca la skill che ti serve prima di qualsiasi altra skill della stessa fonte.

Se nessuno dei due casi è il tuo, verifica la presenza di frontmatter che non è stato analizzato.

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 il cui level è l'impostazione del livello di sforzo quando l'hook viene eseguito, incluso il livello di una skill, di un subagent o di ultrathink: "low", "medium", "high", "xhigh" o "max". Presente per PreToolUse, PostToolUse, Stop, SubagentStop e altri eventi con un contesto di utilizzo degli strumenti, sui modelli che supportano lo sforzo. Se il modello non supporta il tuo livello, level è il livello effettivamente eseguito. Un livello che hai cambiato può comparire qui prima di entrare in vigore. $CLAUDE_EFFORT contiene lo stesso livello.
hook_event_name Nome dell'evento che si è attivato

agent_id e agent_type indicano al tuo script in quale agente si è attivato un hook, ad esempio un subagent, un compagno di squadra in-process o l'agente che hai scelto con --agent:

Campo Descrizione
agent_id Identificatore univoco del subagent o del compagno di squadra in-process in cui si attiva l'hook.
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

Quando l'esecuzione del tuo hook termina, Claude Code usa il suo codice di uscita per decidere se continuare con l'azione che lo ha attivato, come una chiamata a uno strumento o un prompt:

  • 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. Hook che avviano un processo in background descrive l'eccezione.
  • 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, tranne che per gli hook il cui fallimento blocca l'azione, ad esempio un hook con onFailure impostato su "block". Su eventi come PreToolUse, vedi un avviso <hook name> hook error nella trascrizione.

Ciò che il tuo hook stampa su stdout può cambiare il risultato. Ad esempio, quando un hook PreToolUse esce con 1 ma stampa JSON che supera la convalida, quel JSON può comunque 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. Per gli hook il cui fallimento blocca l'azione, l'esecuzione conta invece 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. Quando il tuo hook esce con 0 ma stampa JSON che non può essere analizzato o non supera la convalida dello schema, l'azione procede a meno che l'hook non sia uno di quelli il cui fallimento blocca l'azione. Invece di un successo con i tuoi campi applicati, l'esecuzione è un errore non bloccante.

Per correggere il tuo output, usa il messaggio di analisi o di convalida riportato dall'avviso <hook name> hook error. Per un hook che non è async, il tuo stdout conta come JSON output solo quando stampi un unico oggetto JSON, su una riga o su più righe, circondato solo da spazi bianchi. Claude Code legge qualsiasi altro stdout come testo semplice o come errore di analisi:

  • Output che non inizia con {, oppure che inizia con { e non finisce con }: testo semplice. In base a questa regola, un array JSON e una stringa JSON tra virgolette sono testo semplice.
  • Due o più righe che si analizzano ciascuna come JSON da sole, la prima che inizia con { e l'ultima che finisce con }: testo semplice quando nessuna riga è un oggetto JSON output che imposta un campo. Quando una lo è, l'output è un errore di analisi, quindi stampa quell'oggetto senza nient'altro su stdout.
  • Qualsiasi altra cosa che inizia con { e finisce con } ma non è JSON valido: errore di analisi.

Per la maggior parte degli eventi, Claude Code scrive stdout nel log di debug e non lo mostra nella trascrizione. Su questi eventi, il tuo stdout raggiunge Claude o il riepilogo della compattazione:

  • UserPromptSubmit, UserPromptExpansion, SessionStart e PostModelSwitch: Claude Code aggiunge lo stdout in testo semplice come contesto che Claude può vedere e su cui può agire. Non aggiunge lo stdout che non è riuscito ad analizzare.
  • PreCompact: il tuo stdout diventa istruzioni per il riepilogo della compattazione

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

Un avviso <hook name> hook error con Failed with non-blocking status code: significa che il codice di uscita del tuo hook non era 0 o 2 e il suo stdout era testo semplice o vuoto. A meno che l'hook non sia uno di quelli il cui fallimento blocca l'azione, si tratta di un errore non bloccante, quindi il tuo hook non ha interrotto l'azione. Per acquisire lo stderr completo del tuo hook anziché la prima riga mostrata dall'avviso, abilita il debug logging.

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

Se l'avviso riporta il messaggio No such file or directory della shell, come nella riga seguente, l'hook non è mai stato eseguito, ad esempio perché il percorso del suo script non esiste:

Failed with non-blocking status code: /bin/sh: /path/to/hook.sh: No such file or directory

Se il percorso è sbagliato, correggilo dove l'hook è configurato. 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. A meno che l'hook non sia uno di quelli il cui fallimento blocca l'azione, anche un hook che non riesce ad avviarsi è un errore non bloccante.

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 l'azione quando l'hook non riesce ad avviarsi, imposta onFailure: "block".

[!WARNING]

Il codice di uscita 1 senza output JSON valido è un errore non bloccante a meno che l'hook non sia uno di quelli il cui fallimento blocca l'azione, anche se 1 è il codice di errore Unix convenzionale. Se il tuo hook è destinato ad applicare una policy, usa exit 2.

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, un hook command, http o mcp_tool scaduto non blocca la chiamata allo strumento, tranne che per gli hook il cui fallimento blocca l'azione. La chiamata continua attraverso il normale flusso dei permessi, quindi non contare su un hook bloccato perché agisca da controllo. Per bloccare la chiamata quando un hook command o http va in timeout, imposta onFailure: "block". Un hook di callback dell'Agent SDK che supera il suo timeout blocca la chiamata allo strumento.

Hook che avviano un processo in background

Un hook può avviare un programma di lunga durata, come un server di sviluppo, e lasciarlo in esecuzione mentre la sessione continua. Nello script dell'hook o nel comando in forma shell:

  • Esegui il programma in background
  • Reindirizza il suo stdout e il suo stderr. Ad esempio, il comando Bash npm run dev > dev-server.log 2>&1 & li invia entrambi a un file. Se non li reindirizzi, un hook che non è async impiega fino a 10 secondi per terminare dopo l'uscita del proprio processo.

Se il tuo command hook prende la sua decisione in un processo in background ed esce, un deny che quel processo stampa dopo che Claude Code ha smesso di leggere non blocca la chiamata allo strumento. Per un hook che non è async, Claude Code finisce di leggere l'output dell'hook poco dopo l'uscita del processo stesso dell'hook.

Stampa la decisione prima di avviare un processo in background, e reindirizza lo stdout e lo stderr di quel processo. Se il tuo script stampa un allow e poi avvia un processo in background senza reindirizzarne lo stdout e lo stderr, Claude Code blocca la chiamata allo strumento. L'errore dell'hook include stdio went quiet e refusing to honor the suspect capture.

Questo script di hook PreToolUse stampa la decisione con policy-check.sh, poi carica le metriche in background:

#!/bin/bash
"$CLAUDE_PROJECT_DIR"/.claude/hooks/policy-check.sh || exit $?
"$CLAUDE_PROJECT_DIR"/.claude/hooks/upload-metrics.sh >/dev/null 2>&1 &

Il resto dello script fa quanto segue:

  • || exit $?: rende il codice di uscita del controllo quello dell'hook stesso, così un controllo che esce con 2 per negare blocca comunque. Altrimenti l'hook esce con il codice dell'ultima riga, che è 0.
  • >/dev/null 2>&1 &: esegue upload-metrics.sh in background con il suo output reindirizzato, così non mantiene aperto l'output dell'hook.

Per provare lo script, fai uscire policy-check.sh con 2, poi attiva una chiamata a uno strumento a cui corrisponde l'hook. La chiamata allo strumento viene bloccata.

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. Un hook di policy con un percorso sbagliato o uno script che si arresta in modo anomalo lascia quindi passare tutto. Per bloccare invece l'azione, imposta "onFailure": "block" su un hook command o http. Richiede Claude Code v2.1.295 o successivo. Per gli hook di callback dell'Agent SDK, consulta Bloccare l'azione quando un callback fallisce.

[!NOTE]

Su Windows senza Git Bash, un command hook PreToolUse o PermissionRequest in forma shell che fallisce senza onFailure impostato può invece bloccare la chiamata allo strumento.

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

Hook falliti su Windows senza Git Bash

Se l'errore di una chiamata a uno strumento bloccata include questo testo, un command hook PreToolUse o PermissionRequest è fallito o è andato in timeout e ha bloccato la chiamata:

blocking because Git Bash wasn't found, so only exit code 0 or 2 counts as this hook's verdict.

Se l'errore dice anche PowerShell started no program, inizia dalle correzioni per un command composto solo da stringhe tra virgolette, che PowerShell stampa invece di eseguire.

Senza Git Bash, qualsiasi codice di uscita diverso da 0 o 2 di quell'hook conta come un fallimento, anche quando stampa JSON che non nega la chiamata. Per scoprire perché l'hook è fallito, controlla il log di debug. Poi scegli la correzione adatta al tuo hook:

  • La chiamata deve procedere quando l'hook fallisce: aggiungi "onFailure": "continue" all'hook
  • L'hook esce con 1 per avvisare ma deve bloccare quando si arresta in modo anomalo: con "onFailure": "continue", la chiamata procede con il codice di uscita 1, che si tratti di un avviso o di un arresto anomalo. Aggiungi invece "onFailure": "block", e termina lo script con il codice di uscita 0 nei punti in cui avvisa, così quelle chiamate procedono. Per mostrare comunque l'avviso su PreToolUse, stampalo su stdout nel campo systemMessage di un unico oggetto JSON.
  • L'hook è scritto per Bash: senza Git Bash, un hook in forma shell senza shell impostato viene eseguito in PowerShell, dove può fallire. Installa Git for Windows, oppure imposta CLAUDE_CODE_GIT_BASH_PATH se Git Bash è installato in una posizione in cui Claude Code non lo trova. Poi avvia una nuova sessione.
  • L'hook esegue un solo .exe, non un file .cmd o .bat: metti il programma in command e i suoi argomenti in args per usare la forma exec, e aggiungi "onFailure": "block". Claude Code avvia quindi il programma senza shell. Senza "onFailure": "block", la chiamata può procedere quando il programma fallisce, manca o va in timeout
  • Un hook di controllo PowerShell che deve bloccare in caso di fallimento: senza onFailure impostato, smette di farlo una volta che Git for Windows è installato e Claude Code trova Git Bash. Aggiungi "onFailure": "block" in modo che un fallimento o un timeout blocchi la chiamata con o senza Git Bash. Imposta anche "shell": "powershell" in modo che da quel momento l'hook continui a essere eseguito in PowerShell invece che in Git Bash

Prima della v2.1.309, il messaggio era blocking because Git Bash wasn't found and this hook doesn't set "shell" to "powershell", so only exit code 0 or 2 counts as its verdict.

PowerShell started no program

Un hook con questo errore è stato eseguito in PowerShell, che stampa un command composto solo da stringhe tra virgolette, come "command": "\"C:\\Tools\\guard.exe\"", invece di eseguirlo.

  • Per stampare testo, come JSON: imposta "shell": "powershell", e l'hook esce con 0
  • Per eseguire un file .ps1, .cmd o .bat, oppure un .exe il cui percorso contiene una variabile: imposta "shell": "powershell" e metti & prima del percorso tra virgolette. Lascia $HOME così com'è scritto, e scrivi le variabili d'ambiente come l'esempio check.ps1 scrive $env:CLAUDE_PROJECT_DIR
  • Per eseguire un solo .exe in un percorso senza $: rimuovi le virgolette interne da command, poi applica la correzione per un hook che esegue un solo .exe, scrivendo "args": [] se il programma non accetta argomenti
  • Per eseguire uno script scritto per Bash: applica la correzione per un hook scritto per Bash

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. Per bloccare l'azione quando la richiesta fallisce o restituisce uno stato non-2xx, imposta onFailure: "block". 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.

Dopo un errore non bloccante, l'azione procede a meno che l'hook non sia uno di quelli il cui fallimento blocca l'azione. Tranne che sugli eventi con un proprio contratto di fallimento nella tabella per evento, come WorktreeCreate, ottieni questi risultati per ciascuna risposta o fallimento:

  • 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
  • Errore di connessione: errore non bloccante
  • Timeout: l'hook viene annullato, come descritto in Timeouts

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.

[!NOTE]

Scegli un approccio per hook: usa solo i codici di uscita per la segnalazione, oppure esci con 0 e stampa JSON per il controllo strutturato. Se li mescoli, exit 2 mantiene il suo effetto di blocco, e Claude Code legge comunque i campi JSON, con l'unica eccezione relativa all'elicitazione indicata in Exit code 2.

Non stampare su stdout nient'altro che l'oggetto JSON. Per un hook che non è async, qualsiasi altro testo presente, come una riga che il profilo della tua shell stampa all'avvio, impedisce a Claude Code di leggere l'oggetto come JSON, e Hook JSON has no effect mostra come trovare e silenziare quel testo.

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: tranne che per lo stdout di PreCompact, Claude Code salva l'output in un file nella directory della sessione. Sostituisce l'output 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 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 alcuni limiti:

  • Claude Code scrive la sequenza solo in una sessione interattiva, e solo mentre la sua interfaccia è aperta. In modalità non interattiva con il flag -p e nell'Agent SDK, ignora il campo.
  • Il campo funziona solo da un hook che non viene eseguito in background, quindi lascia disattivati async e asyncRewake.
  • 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.

Se usi tmux o GNU screen, completa prima la configurazione per questi strumenti. L'esempio seguente attiva una notifica desktop da un hook Notification in un terminale che legge OSC 777, come Ghostty o Warp. La chiamata jq -nc --arg dello script costruisce l'output JSON e sottopone a escape eventuali virgolette, barre rovesciate e a capo nel messaggio di notifica:

#!/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.

Ricevere notifiche del terminale all'interno di tmux o GNU screen

Quando Claude Code viene eseguito all'interno di tmux o GNU screen, le sequenze OSC richiedono quanto segue per raggiungere la tua applicazione di terminale:

  • tmux 3.3 o successivo: aggiungi set -g allow-passthrough on a ~/.tmux.conf, poi esegui tmux source-file ~/.tmux.conf da una shell. L'opzione si applica a ogni programma in quei riquadri, non solo a Claude Code. Con on, solo i riquadri che tmux sta mostrando possono inviarle. Per consentire di inviarle anche agli altri riquadri di una sessione a cui sei collegato, usa all al posto di on, su tmux 3.4 o successivo.
  • GNU screen: mantieni visibile in screen la finestra di Claude Code, perché le sequenze provenienti da una finestra in background non raggiungono la tua applicazione di terminale

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 la tua stringa contiene un tag <system-reminder> o </system-reminder>, Claude riceve la stringa con il < di quel tag sostituito da &lt;.

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. In una sessione cloud di Cowork, Claude Code ignora updatedToolOutput quando lo strumento è AskUserQuestion o ExitPlanMode, e Claude legge la risposta o il piano originale. 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:

[tab] Decisione di livello superiore

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"
}

[tab] PreToolUse

Usa hookSpecificOutput per un controllo più ricco: consentire, negare o inoltrare la decisione all'utente. Puoi anche modificare l'input dello strumento prima che venga eseguito o iniettare contesto aggiuntivo per Claude. Consulta PreToolUse decision control per l'insieme completo di opzioni.

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Database writes are not allowed"
  }
}

[tab] PermissionRequest

Usa hookSpecificOutput per consentire o negare una richiesta di permesso per conto dell'utente. Quando consenti, puoi anche modificare l'input dello strumento o applicare regole di permesso in modo che all'utente non venga chiesto di nuovo. Consulta PermissionRequest decision control per l'insieme completo di opzioni.

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

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 degli 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 si attiva l'evento, 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 contesto di sviluppo come issue esistenti o modifiche recenti al tuo codebase, o per impostare variabili d'ambiente. Per un contesto statico che non richiede uno script, usa invece CLAUDE.md.

SessionStart viene eseguito a ogni sessione, quindi mantieni questi hook veloci. Sono supportati solo gli hook type: "command" e type: "mcp_tool". Consulta Campi degli hook di 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 si attiva
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

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 che hai ripreso appare senza attendere gli hook. La prima risposta di Claude attende comunque che gli hook terminino, così il loro contesto raggiunge Claude.

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

La stessa attesa si applica all'avvio, inclusa 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 l'esecuzione.

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 segnalare quanto costa riprendere una conversazione datata prima della prima richiesta, ad esempio in un systemMessage. Questi campi richiedono Claude Code v2.1.251 o successivo.

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 reinvia 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 della scrittura di 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 utente, impostare il titolo della sessione, osservare file e ricaricare le skill. Restituisci il campo per 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 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 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 osservare per gli eventi FileChanged durante questa sessione
reloadSkills Booleano. Quando è true, Claude Code riesamina 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 al contesto di Claude lo stdout in testo semplice di un hook SessionStart.

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 l'esecuzione degli hook SessionStart.

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 di esso 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 il nuovo esame:

#!/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 comandi shell che Claude esegue più avanti nella sessione.

Per impostare singole variabili d'ambiente, scrivi istruzioni export in CLAUDE_ENV_FILE. Usa l'aggiunta in coda (>>) 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 provenienti 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

Ogni comando Bash esegue il contenuto del file come codice shell prima del comando stesso, quindi una riga lì può usare qualsiasi cosa Bash valuti, come il riferimento a $PATH in export PATH="$PATH:./node_modules/.bin".

Variabili persistenti nei comandi PowerShell

Anche i comandi PowerShell ricevono le variabili da CLAUDE_ENV_FILE, in Claude Code v2.1.296 o successivo, ma PowerShell non esegue mai il file. Claude Code legge invece le assegnazioni dal file e le copia nell'ambiente del comando PowerShell. Lo fa solo quando ogni riga è di uno dei tipi seguenti, considerando ciò che ogni hook di questa sessione ha scritto e qualsiasi script su cui hai impostato CLAUDE_ENV_FILE prima dell'avvio:

  • Una riga vuota o un commento #
  • Un'assegnazione all'inizio della riga, scritta export NAME=value, declare -x NAME=value o NAME=value, con un valore che Bash userebbe esattamente come scritto, composto da qualsiasi combinazione di queste parti: testo senza virgolette che usa solo lettere, cifre e i caratteri _ @ % + = : , . / -, testo tra virgolette singole e testo tra virgolette doppie in cui ogni $, backtick o " al suo interno è preceduto da una barra rovesciata di escape

Se una qualsiasi riga è di un altro tipo, come export PATH="$PATH:./node_modules/.bin" con il suo $PATH senza escape, un comando source o le stringhe $'...' che direnv export bash stampa, i comandi PowerShell non ricevono nessuna delle variabili e claude --debug registra Session environment is not all plain assignments. I comandi Bash le ricevono comunque tutte. Su Windows, i comandi PowerShell non ricevono inoltre una variabile il cui valore contiene / o \, perché Git Bash e Windows scrivono i percorsi in modo diverso. Un comando PowerShell in sandbox non riceve nessuna delle variabili.

[!NOTE]

CLAUDE_ENV_FILE è disponibile per gli hook SessionStart, Setup, CwdChanged e FileChanged. Gli altri eventi degli hook non hanno accesso a questa variabile, e nemmeno un hook eseguito in PowerShell, sia tramite "shell": "powershell" sia per impostazione predefinita su Windows senza Git Bash.

Setup

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

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

Matcher Quando si attiva
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 sul terminale. Per verificare che gli hook siano stati eseguiti, avvia con claude --debug-file <path> --init-only, sostituendo <path> con la posizione di un file di log, e controlla nel log le voci degli hook Setup e SessionStart.

Poiché Setup non si attiva a ogni avvio, un plugin che necessita dell'installazione di una dipendenza non può basarsi solo su Setup. Lo schema pratico è verificare la dipendenza al primo utilizzo e installarla se manca, ad esempio 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 idonee dei pacchetti Node.js 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 compaiono 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. Se il tuo hook scrive variabili in quel file, queste 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 di strumenti MCP.

InstructionsLoaded

Si attiva quando un file CLAUDE.md o .claude/rules/*.md viene caricato nel contesto. Questo evento si attiva all'avvio della sessione per i file caricati subito e di nuovo più tardi 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 a scopo di osservabilità.

Questo evento non si attiva quando Claude legge direttamente AGENTS.md tramite l'impostazione Project instructions. Si attiva invece 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 ad 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 Motivo per cui il file è stato caricato: "session_start", "nested_traversal", "path_glob_match", "include" o "compact". Il valore "compact" si attiva 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 log di audit, monitoraggio della conformità o osservabilità.

UserPromptSubmit

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

Gli hook UserPromptSubmit non si attivano 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 blocca 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 di 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 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é un callback in quel punto può fungere da controllo di policy che non deve fallire in modo permissivo. La sessione continua.

Esc durante il controllo di un prompt

Se premi Esc o interrompi la sessione mentre gli hook stanno ancora controllando un prompt, il controllo si interrompe e il prompt non raggiunge Claude. Il testo che hai digitato nel terminale torna nell'input del prompt, e gli hook lo controllano di nuovo quando lo reinvii.

Un hook può bloccare il prompt nello stesso momento in cui interrompi. Il prompt rimane quindi bloccato: vedi il messaggio di blocco di quell'hook e il testo non torna nell'input.

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"
}

Quando il prompt è una skill o un comando personalizzato che hai digitato, come /deploy staging, l'hook riceve quella riga così come l'hai digitata anziché il contenuto espanso della skill. Se l'hook blocca il prompt, il comando non viene eseguito, quindi non vengono eseguiti nemmeno i comandi shell che la skill inserisce con !`<command>`. Per basare la corrispondenza sul nome o sugli argomenti del comando, usa invece UserPromptExpansion.

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 ciascuno inseriti come promemoria di sistema che inizia con il nome dell'hook; Claude li legge entrambi. Per verificare 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, omette il testo del prompt dal messaggio di blocco. Consulta Cosa lascia dietro di sé un prompt bloccato

Un hook che blocca uscendo con 2 viene instradato allo stesso modo 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 dietro di sé 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 della trascrizione della sessione su disco. Per omettere il testo dal messaggio, stampa JSON con "suppressOriginalPrompt": true all'interno di hookSpecificOutput. Funziona sia che l'hook blocchi con decision: "block" sia che esca con 2.

suppressOriginalPrompt modifica solo il messaggio di blocco. Il testo inviato può comunque apparire in file locali come la trascrizione della sessione e la cronologia dei prompt, quindi un hook di blocco non è un modo per tenere un segreto lontano 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 si attiva solo quando Claude chiama lo strumento, ma digitare direttamente /skillname aggira PreToolUse. UserPromptExpansion si attiva su quel percorso diretto.

Corrisponde su command_name. Lascia il matcher vuoto 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 viene instradato allo stesso modo 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 visualizza il messaggio in incrementi: ogni volta che un gruppo di righe appena completate è pronto per il rendering, 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 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 senza modifiche.

MessageDisplay non supporta i matcher e si attiva 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 l'intero testo del messaggio: index è 0, final è true e delta contiene l'intero messaggio. Un hook che raccoglie il testo di delta per ogni messaggio riceve lo stesso testo complessivo 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 monitorare l'avanzamento in 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 i marcatori del grassetto e i backtick del codice inline da delta e restituisce il risultato come displayContent.

[tab] macOS/Linux

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("`"; ""))}}'

[tab] Windows (PowerShell)

Registra un hook di comando che esegue lo script tramite PowerShell:

{
  "hooks": {
    "MessageDisplay": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "powershell.exe",
            "args": [
              "-NoProfile",
              "-ExecutionPolicy",
              "Bypass",
              "-File",
              "${CLAUDE_PROJECT_DIR}/.claude/hooks/plain-display.ps1"
            ]
          }
        ]
      }
    ]
  }
}

Il flag -NoProfile salta il caricamento del tuo profilo PowerShell così l'hook si avvia rapidamente, e -ExecutionPolicy Bypass consente a PowerShell di eseguire il file di script locale.

Salva questo script in .claude/hooks/plain-display.ps1 nel tuo progetto:

$batch = [Console]::In.ReadToEnd() | ConvertFrom-Json
$text = $batch.delta -replace '\*\*', '' -replace '`', ''
@{
  hookSpecificOutput = @{
    hookEventName = "MessageDisplay"
    displayContent = $text
  }
} | ConvertTo-Json

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

PreToolUse

Viene eseguito dopo che Claude ha creato i parametri dello strumento e prima dell'elaborazione della chiamata allo strumento. Corrisponde a qualsiasi nome di strumento eccetto 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 invece di far corrispondere per nome gli strumenti di modifica dei file. A differenza di PreToolUse, Claude Code esegue gli hook FileChanged dopo la modifica, e non hanno controllo delle decisioni, quindi non possono bloccare la scrittura.

[!WARNING]

PreToolUse viene eseguito solo quando Claude chiama uno strumento. Per impostazione predefinita, i file che referenzi con @ nel tuo prompt vengono aggiunti senza alcuna chiamata a uno strumento. Claude Code ne inserisce il contenuto durante la costruzione del prompt, quindi nessun hook PreToolUse si attiva per essi, inclusi gli hook che corrispondono a Read. PreToolUse inoltre non si attiva per EndConversation.

Puoi bloccare percorsi specifici dai riferimenti @, oppure disattivare l'inserimento:

  • Bloccare percorsi specifici: aggiungi una regola di negazione Read per essi
  • Disattivare l'inserimento: imposta CLAUDE_CODE_DISABLE_FILE_MENTIONS=1 nella tua shell, nelle impostazioni utente o nelle impostazioni gestite. Claude riceve quindi i percorsi menzionati con una nota che gli chiede di leggerli usando i suoi strumenti per i file. Se lo fa, i tuoi hook PreToolUse corrispondenti vengono eseguiti. La variabile richiede Claude Code v2.1.299 o successivo, e Claude Code la ignora nelle impostazioni di progetto e locali.

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

Un hook callback dell'Agent SDK su PreToolUse che supera il timeout blocca la chiamata allo strumento.

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 trattarne uno 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 successivo.

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 effettua la corrispondenza sui percorsi non può essere aggirato tramite ~ o una scrittura relativa dello stesso percorso
  • Su Windows, il percorso arriva con separatori a barra rovesciata, anche quando il tuo hook viene eseguito in Git Bash, dove $PWD appare come /c/project
  • Un confronto scritto con barre normali, come un controllo su /src/, non corrisponde mai a un percorso con barre rovesciate, 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 ^, dato che 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 includono 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 successivo.

[!NOTE]

L'elenco è fornito al meglio delle possibilità ed è in beta pubblica. Claude Code può non rilevare una modifica, includere un file che un altro processo ha modificato nello stesso momento o fermarsi ai suoi limiti di dimensione. La struttura del campo può cambiare. Usa l'elenco per trovare cosa rivedere, non per applicare una policy.

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, come quella di un subagent, è stata eseguita nello stesso repository nello stesso momento, quindi 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

Fai corrispondere Bash|PowerShell negli hook che ispezionano i comandi shell, in modo che coprano entrambi gli strumenti:

  • Su Windows, ovunque sia abilitato lo strumento PowerShell, Claude tratta PowerShell come shell principale e vi instrada i comandi shell.
  • 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 si attiva mai lì.
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 successivo

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 di questi domini
blocked_domains array ["spam.example.com"] Facoltativo: esclude i risultati di questi domini
mode string "extended" Facoltativo: tipo di ricerca. "standard" richiede una ricerca più leggera e "extended" la ricerca completa. Alcune sessioni non inviano mode, e una chiamata senza di esso esegue la ricerca completa. Richiede Claude Code v2.1.282 o successivo
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 quello 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 sapere quali subagent vengono eseguiti in ciascun modo, consulta Eseguire i subagent in primo piano o in background
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 resoconto 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
totalTokens number 12450 Numero di token dalla richiesta API finale del subagent: token di input, di output e di 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 agli strumenti effettuate dal subagent
usage object {"input_tokens": 8320, ...} Ripartizione per tipo dei token della richiesta API finale: input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens

In Claude Code v2.1.271 o successivo, un subagent eseguito con lo strumento SubagentHandback, che Claude Code fornisce in modalità auto, consegna il suo resoconto tramite quello strumento anziché restituirlo come testo. Il campo content del suo risultato completed contiene quindi una breve nota su quella consegna anziché il resoconto stesso. Per leggere il resoconto, 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 al momento di quella transizione. Contiene status: "async_launched", agentId, description, prompt, outputFile e resolvedModel.

In una risposta completed, resolvedModel indica il modello con cui è partito il subagent, 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 vi si riflette.

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 proveniente dal modello è tipicamente 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ù 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 (consentire, negare, chiedere conferma o differire) più la possibilità di modificare l'input dello strumento prima dell'esecuzione. Per consentire o riscrivere un comando Monitor, consulta Confrontare i comandi Monitor.

Campo Descrizione
permissionDecision "allow" salta la richiesta di permesso, tranne che per le azioni che nessuna modalità approva automaticamente e le letture da percorsi di rete. Non salta nemmeno l'approvazione per le chiamate agli strumenti che un'app dell'Agent SDK contrassegna come always_ask, a meno che la modalità di permesso non approvi la chiamata. AskUserQuestion ed ExitPlanMode richiedono updatedInput abbinato a "allow". Per il plan mode, consulta Approvazioni degli hook nel plan mode. "deny" impedisce la chiamata allo strumento. "ask" chiede all'utente di confermare. "defer" termina il turno in modo che lo strumento possa essere ripreso in seguito e richiede la modalità non interattiva con il flag -p. Le regole di negazione e di richiesta di conferma vengono comunque valutate indipendentemente da ciò che l'hook restituisce
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 in base all'input restituito dal tuo hook, non all'input inviato da Claude. Combinalo con "allow" per approvare automaticamente, oppure con "ask" per mostrare all'utente l'input modificato. Per "defer", viene ignorato. Senza permissionDecision, la modalità auto può negare la chiamata modificata
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, l'ordine di precedenza è deny > defer > ask > allow.

Un hook che blocca uscendo con 2 viene instradato allo stesso modo 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.

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

[!NOTE]

In precedenza PreToolUse usava i campi di primo livello decision e reason, ma questi sono deprecati per questo evento. Usa invece hookSpecificOutput.permissionDecision e hookSpecificOutput.permissionDecisionReason. I valori deprecati "approve" e "block" corrispondono rispettivamente a "allow" e "deny". Altri eventi come PostToolUse e Stop continuano a usare decision e reason di primo livello come formato attuale.

updatedInput di PreToolUse in modalità auto

Quando il tuo hook restituisce updatedInput senza permissionDecision in modalità auto, Claude Code può negare la chiamata modificata. Il server ha esaminato l'input scritto da Claude, non l'input restituito dal tuo hook. Nelle sessioni interattive del terminale e nelle sessioni VS Code con una connessione diretta all'API di Anthropic, la negazione include questo testo:

a hook changed this call's input after auto mode reviewed it

Dopo una negazione con questo testo, Claude può emettere di nuovo la chiamata modificata in modo che il server la esamini. Se Claude ripete esattamente l'input modificato nella sua risposta successiva, il tuo hook viene comunque eseguito:

  • Può negare la chiamata o inviarla alla richiesta di permesso
  • Qualsiasi updatedInput che restituisce viene scartato, quindi ciò che viene eseguito è ciò che il server ha esaminato

Non è necessario modificare il tuo hook per questa negazione, a meno che Claude non ti dica nella sua risposta che la modalità auto non ha potuto verificare la chiamata.

Nelle sessioni che mostrano questo testo, un hook command nelle impostazioni o nel file degli hook di un plugin può modificare le chiamate Bash, PowerShell, Monitor, Edit, Write e NotebookEdit senza questa negazione. La negazione può comunque comparire, ad esempio quando un hook qualsiasi ha modificato una chiamata precedente nella stessa risposta di Claude o quando un comando Bash chiede di essere eseguito fuori dalla sandbox.

Decisioni ask di PreToolUse in modalità auto

In modalità auto, l'"ask" di un hook PreToolUse normalmente invia la chiamata allo strumento alla richiesta di permesso, e il classificatore non la decide. Alcune chiamate seguono un percorso diverso:

  • Chiamate segnalate anche dai controlli interni di Claude Code: includono le scritture in percorsi protetti e i comandi che chiedono di essere eseguiti fuori dalla sandbox. Il classificatore può negare una di queste chiamate prima che compaia qualsiasi richiesta. Se la approva, la chiamata normalmente va comunque alla richiesta di permesso.
  • Chiamate SubagentHandback: questo strumento consegna il report di un subagent. Per esso non compare alcuna richiesta, e la chiamata viene eseguita anche se il classificatore si oppone.

Per fermare una chiamata SubagentHandback, restituisci "deny". Claude Code comunica quindi al subagent, fino a tre volte, che il suo report non è stato consegnato e che deve chiamare di nuovo lo strumento. Se il tuo hook continua a negare, il subagent termina senza consegnare un report tramite questo strumento, e la conversazione in attesa riceve una nota che lo segnala.

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 ha un host dei permessi che riceva la richiesta, come un callback canUseTool dell'Agent SDK.

Un hook PreToolUse soddisfa questo 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 updatedInput contenente la risposta, così lo strumento viene eseguito senza chiedere conferma

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 ciascuna 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ò verificare 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 ne leggono l'output JSON, come un'app Agent SDK o un'interfaccia personalizzata costruita su Claude Code. Consente a quel processo chiamante di mettere in pausa Claude in corrispondenza di una chiamata a uno strumento, raccogliere input tramite la propria interfaccia e riprendere da dove si era interrotto.

Claude Code rispetta "defer" solo in modalità non interattiva con il flag -p, e solo quando Claude effettua una singola chiamata a uno strumento nel turno:

  • Nelle sessioni interattive: registra un avviso e ignora il risultato dell'hook
  • Più chiamate agli strumenti contemporaneamente: "defer" viene ignorato con un avviso e lo strumento procede attraverso il normale flusso dei permessi

Lo strumento AskUserQuestion è il caso tipico: Claude vuole chiedere qualcosa all'utente, ma non c'è un terminale in cui rispondere. Un'esecuzione -p offre AskUserQuestion solo quando ha 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 si attiva.
  2. L'hook restituisce permissionDecision: "defer". Lo strumento non viene eseguito. Il turno termina con un risultato che contiene stop_reason: "tool_deferred", e la chiamata allo strumento in sospeso rimane 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:

{
  "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 un timeout né un limite di nuovi tentativi. La sessione rimane su disco finché la pulizia periodica di cleanupPeriodDays non la elimina, dopo 30 giorni per impostazione predefinita.

Se la risposta non è pronta quando riprendi, l'hook può restituire di nuovo "defer". Se hai inviato un prompt con quella ripresa, Claude risponde senza il risultato dello strumento e non esegue la chiamata. Riprendi senza un prompt finché l'hook non restituisce "allow" o "deny".

[!NOTE]

Per riprendere una sessione differita in plan mode, passa --permission-prompt-tool insieme a --resume in modo che Claude Code possa presentare il piano per l'approvazione. Per le altre condizioni, consulta Riprendere in plan mode con -p. Richiede Claude Code v2.1.246 o successivo.

Quando riprendi con -p, Claude Code non ripristina nessun'altra modalità di permesso memorizzata. Avvia l'esecuzione nella modalità di permesso in cui partirebbe una nuova esecuzione claude -p, quindi passa di nuovo --permission-mode o --dangerously-skip-permissions se la sessione rinviata ne usava uno.

Riprendere quando uno strumento rinviato non è disponibile

Un risultato con stop_reason: "tool_deferred_unavailable" indica che lo strumento di una chiamata rinviata non è disponibile nella sessione ripresa, ad esempio perché il suo server MCP non è connesso. Il tuo hook PreToolUse non si attiva, e il campo deferred_tool_use del risultato indica lo strumento.

Finché la chiamata è in sospeso, qualsiasi prompt che invii non raggiunge Claude e l'esecuzione termina con codice 1. Il campo result inizia con <tool> isn't available, so its deferred call didn't run. Esegui la chiamata, oppure scartala in modo che non venga eseguita:

  • Per eseguire la chiamata: rendi disponibile lo strumento e riprendi di nuovo.
  • Per scartare la chiamata: riprendi con un prompt e con CLAUDE_CODE_DROP_UNAVAILABLE_DEFERRED_TOOL impostata su 1. Claude riceve un errore come risultato della chiamata e risponde al tuo prompt. Quando riprendi in seguito, nessuna chiamata è in sospeso.

Quando scarti la chiamata, l'esecuzione scrive il risultato tool_deferred_unavailable con un campo result vuoto, poi la risposta di Claude in un secondo risultato. Termina con codice 0 a meno che quella risposta non sia un errore. Se imposti la variabile e non invii alcun prompt, l'esecuzione scarta comunque la chiamata e termina con codice 1.

Prima della v2.1.319, Claude Code rispondeva a un prompt senza il risultato dello strumento e scartava la chiamata. Su quelle versioni, invia un nuovo prompt una volta che lo strumento è disponibile.

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 allow 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 proprie opzioni dalla richiesta stessa. Una finestra di dialogo che lo legge può comunque nascondere un'opzione il cui suggerimento rimane nell'array, ad esempio quando allowManagedPermissionRulesOnly nasconde le opzioni per salvare le regole. Può anche offrire opzioni che non hanno una 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 un permesso, o quando altrimenti negherebbe automaticamente una chiamata che non può mostrare una richiesta. Nessuno dei due eventi si attiva 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, lo script del tuo hook può restituire un oggetto decision con questi campi specifici dell'evento:

Campo Descrizione
behavior "allow" concede il permesso, "deny" lo nega. Le regole deny e ask vengono comunque valutate, quindi un hook che restituisce "allow" non sovrascrive una regola deny 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 non modificati insieme a quelli modificati. L'input modificato viene rivalutato rispetto alle regole deny e ask. AskUserQuestion e ExitPlanMode richiedono questo campo
updatedPermissions Solo per "allow": array di voci di aggiornamento dei permessi da applicare, come l'aggiunta di una regola allow o la modifica della modalità di permesso della sessione
message Solo per "deny": indica a Claude perché il permesso è stato negato
interrupt Solo per "deny": se true, arresta Claude

Per consentire o riscrivere un comando Monitor, consulta Corrispondenza dei comandi Monitor.

Un hook che termina 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 gli 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

In rules, per una regola di permesso scritta Tool(specifier), toolName è il nome dello strumento e ruleContent è lo specificatore:

  • Bash(npm test): { "toolName": "Bash", "ruleContent": "npm test" }
  • Bash o Bash(*): { "toolName": "Bash" }, perché entrambi corrispondono all'intero strumento
  • mcp__github__get_issue: { "toolName": "mcp__github__get_issue" }

Non includere ruleContent in una regola allow per uno strumento MCP. Se lo aggiungi, la regola non corrisponde a nessuna chiamata.

[!NOTE]

setMode con bypassPermissions ha effetto solo se hai avviato la sessione con la modalità bypass già disponibile: --dangerously-skip-permissions, --permission-mode bypassPermissions, --allow-dangerously-skip-permissions oppure permissions.defaultMode: "bypassPermissions" nelle impostazioni utente, --settings o impostazioni gestite. Altrimenti l'aggiornamento non ha alcun effetto. L'aggiornamento non ha effetto neanche quando permissions.disableBypassPermissionsMode disabilita la modalità, o quando la sessione si avvia in modalità con restrizioni.

bypassPermissions non viene mai salvato come defaultMode, indipendentemente da destination.

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

destination Scrive in
session solo in memoria, scartato al termine della sessione
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 l'ha scritto, usa FileChanged. Claude Code non esegue un hook PostToolUse che corrisponde a Edit|Write quando un comando Bash o un processo esterno a Claude Code riscrive lo stesso file.

PostToolUse dopo un'interruzione

Se un hook PostToolUse si arresta in anticipo dopo che invii un messaggio o interrompi durante una chiamata a uno strumento, verifica se hai premuto Ctrl+Enter, Enter o Esc:

  • Ctrl+Enter: se premerlo termina il turno mentre uno strumento è in esecuzione e lo strumento termina, i tuoi hook PostToolUse vengono eseguiti sul suo risultato e hanno 10 secondi per terminare. Il tasto agisce come Esc se hai messo in coda un comando ! prima dei tuoi messaggi o se le attività in background sono disabilitate, ad esempio con CLAUDE_CODE_DISABLE_BACKGROUND_TASKS.
  • Enter: un messaggio che metti in coda con Enter non interrompe il turno, quindi gli hook mantengono il loro timeout abituale.
  • Esc: annulli immediatamente gli hook, quindi invia il messaggio con Enter o Ctrl+Enter quando gli hook devono essere eseguiti.

Allo scadere dei 10 secondi, Claude Code termina un hook di tipo command ancora in esecuzione, qualunque sia il suo timeout. Per consentire a un hook di tipo command più lungo di terminare, eseguilo con async: true, il che significa che l'hook non può bloccare o riscrivere il risultato.

Prima della v2.1.319, gli hook non si avviavano dopo che avevi interrotto il turno con un messaggio, e un hook in esecuzione veniva arrestato.

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 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 un feedback a Claude dopo l'esecuzione dello strumento. Oltre ai campi di output JSON disponibili per tutti gli hook, lo script del tuo 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. Vedi Aggiungere contesto per Claude
classifierContext Breve nota sul risultato di questa chiamata destinata al classificatore della modalità auto anziché a Claude. Vedi Annotare un risultato per il classificatore della modalità auto. Richiede Claude Code v2.1.236 o successivo
updatedToolOutput Sostituisce l'output dello strumento con il valore fornito prima che venga inviato a Claude. Il valore deve corrispondere alla forma dell'output dello strumento. In una sessione cloud di Cowork, Claude Code ignora questo campo quando lo strumento è AskUserQuestion o ExitPlanMode, e Claude legge la risposta o il piano originale
updatedMCPToolOutput Sostituisce l'output solo per gli strumenti MCP. Preferisci updatedToolOutput, che funziona anche per gli strumenti integrati

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
    }
  }
}

[!WARNING]

updatedToolOutput cambia solo ciò che vede Claude. Lo strumento è già stato eseguito quando l'hook si attiva, quindi eventuali file scritti, comandi eseguiti o richieste di rete inviate hanno già avuto effetto. Anche la telemetria, come gli span degli strumenti OpenTelemetry e gli eventi di analisi, acquisisce l'output originale prima che l'hook venga eseguito. Per impedire o modificare una chiamata a uno strumento prima che venga eseguita, usa invece un hook PreToolUse.

Il valore sostitutivo deve corrispondere alla struttura dell'output dello strumento. Gli strumenti integrati restituiscono oggetti strutturati anziché semplici stringhe. Ad esempio, Bash restituisce un oggetto con i campi stdout, stderr, interrupted e isImage. Per gli strumenti integrati, un valore che non corrisponde allo schema di output dello strumento viene ignorato e viene usato l'output originale. L'output degli strumenti MCP viene passato senza validazione dello schema. Rimuovere dettagli sugli errori di cui Claude ha bisogno può portarlo a procedere sulla base di un presupposto errato.

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. La nota serve al classificatore per la revisione delle azioni successive. Il campo richiede Claude Code v2.1.236 o versioni successive.

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 inviato da te, ma non rimuove mai un blocco che neanche 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é tale risposta arriva dopo che Claude Code ha registrato il risultato dello strumento
  • Chiamate che il classificatore non registra: la trascrizione del classificatore omette le ricerche 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 la riscrittura viene rifiutata o se la riscrittura di un altro hook la sostituisce. Claude Code consegna una nota restituita senza una riscrittura anche quando un altro hook riscrive l'output

[!WARNING]

Il classificatore legge il contenuto che inserisci in classifierContext come informazione proveniente dall'applicazione che ospita la sessione, quindi non copiarci output di strumenti non attendibili o testo di terze parti. Limita la nota a una breve affermazione su questa singola chiamata, come un fatto sulla sua origine o una dichiarazione dell'utente al riguardo; non usare il campo per consegnare messaggi non correlati o un flusso di eventi.

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 un feedback correttivo a Claude.

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

[!NOTE]

Questo evento non si attiva per le chiamate agli strumenti rifiutate prima dell'esecuzione: un nome di strumento sconosciuto, un input che non supera la validazione dello schema o la validazione specifica dello strumento, oppure un permesso negato. I rifiuti per validazione vengono restituiti come risultati tool_use_error e avvengono prima dell'esecuzione degli hook, quindi non attivano né PreToolUse né PostToolUseFailure. I permessi negati attivano PreToolUse ma non questo evento; vedi PermissionDenied.

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. L'annullamento di 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 da visualizzare, non come un formato stabile.

  • Per Bash e PowerShell, un comando che è stato eseguito ed è terminato produce una prima riga Exit code N, seguita dall'eventuale output prodotto dal comando come un unico blocco con stdout e stderr intercalati
  • Un payload può anche contenere un semplice messaggio di fallimento senza la 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 indicatore ... [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, lo script del tuo hook può restituire questi campi specifici dell'evento:

Campo Descrizione
additionalContext Stringa aggiunta al contesto di Claude insieme all'errore. Vedi 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 punto giusto per inserire contesto che dipende dall'insieme degli 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 strumento lo ha emesso. Per Read, ciò significa testo preceduto dai numeri di riga anziché il contenuto grezzo del file. Le risposte possono essere grandi, quindi analizza solo i campi di cui hai bisogno.

[!NOTE]

La struttura di tool_response è diversa da quella di PostToolUse. PostToolUse passa l'oggetto Output strutturato dello strumento, come {filePath: "...", type: "create"} per Write; PostToolBatch passa il contenuto tool_result serializzato che vede il modello.

Controllo delle decisioni di PostToolBatch

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

Campo Descrizione
additionalContext Stringa di contesto inserita una volta prima della successiva chiamata al modello. Vedi Aggiungere contesto per Claude per i dettagli sulla consegna, cosa inserirvi e 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 successiva chiamata al modello. Il messaggio di blocco proviene dal reason o dallo stopReason JSON, oppure dallo stderr con codice di 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 nega una chiamata a uno strumento, anche quando la nega 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 analizzata correttamente. Questo hook si attiva solo in modalità auto: non viene eseguito quando neghi manualmente una finestra di dialogo dei permessi, quando un hook PreToolUse blocca una chiamata o quando una regola deny corrisponde. Usalo per registrare i rifiuti, regolare la configurazione o indicare 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]; vedi 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 del classificatore, è il testo fisso Classifier unavailable

Controllo delle decisioni di PermissionDenied

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

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

Quando retry è true, Claude Code aggiunge alla conversazione un messaggio che indica al modello che può riprovare la chiamata allo strumento. Claude Code non annulla il rifiuto in sé. 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 analizzata correttamente, 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 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
auth_storage_failure Il tuo accesso viene completato, ma Claude Code non riesce a salvare le nuove credenziali nel proprio archivio delle credenziali. Si attiva al posto di auth_success
elicitation_dialog Un server MCP apre un modulo di elicitation 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'elicitation in modalità URL è completata
elicitation_response Una risposta di elicitation 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 la domanda di configurazione del terminale di un compagno 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 prosegue il tuo task dopo che un limite di utilizzo di claude.ai lo aveva messo in pausa: al ripristino, o prima quando qualcosa che fai in Claude Code durante l'attesa, come aggiungere crediti di utilizzo, eseguire l'upgrade del piano 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 si è ripristinato mentre il tuo computer era in stop per più di circa 30 minuti. Claude Code attende che tu prema Enter invece di proseguire. Dopo uno stop più breve prosegue 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 proseguire il tuo task: autoContinueAtUsageLimit è stato disattivato o il ripristino si è spostato di oltre 24 ore durante un'attesa avviata autonomamente da Claude Code, il task proseguito ha continuato a raggiungere il limite, oppure la prosecuzione è stata bloccata prima di raggiungere il modello. Non si attiva quando premi Esc o Ctrl+C, o scegli Don't continue automatically
plugin_notification Un mod genera una notifica con $.ui.notify. Il title dell'hook è il nome del mod, a meno che il mod non ne abbia impostato uno

I tipi quota_auto_resume_fired, quota_auto_resume_stale e quota_auto_resume_disabled richiedono Claude Code v2.1.234 o successiva. Il tipo auth_storage_failure richiede v2.1.288 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 successivo.

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

[!NOTE]

I tipi permission_prompt, idle_prompt, elicitation_dialog e elicitation_url_dialog condividono la tempistica con le notifiche desktop, quindi nelle sessioni del terminale li vedi solo quando sembri lontano dal terminale:

  • Aspettati permission_prompt una volta che non digiti da circa sei secondi. Il timer parte quando compare la richiesta di permesso e ogni pressione di tasto lo rinvia. Per eseguire un hook immediatamente quando Claude chiede il permesso di usare uno strumento, usa invece PermissionRequest.
  • Aspettati idle_prompt circa 60 secondi dopo che Claude ha finito di rispondere, e solo se da allora non hai digitato nulla e nessun agente in background, come un subagent in background, è ancora in esecuzione. Claude Code non invia idle_prompt mentre attende il ripristino di un limite di utilizzo di claude.ai. Quando l'attesa termina da sola, si attiva invece uno dei tipi quota_auto_resume_*.
  • Aspettati elicitation_dialog per un modulo di elicitation, o elicitation_url_dialog per una richiesta di URL nel browser, una volta che non digiti da circa sei secondi. Entrambi condividono la stessa soglia di sei secondi di permission_prompt: il timer parte quando compare la finestra di dialogo e ogni pressione di tasto lo rinvia.

Una richiesta di permesso o un'elicitation che arriva mentre sullo schermo è presente un'altra finestra di dialogo mantiene la stessa soglia di sei secondi, calcolata dal momento in cui arriva la richiesta. La sua notifica può raggiungerti mentre la richiesta è ancora in attesa dietro la finestra di dialogo aperta.

Claude Code gestisce 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 avvia un subagent con lo strumento Agent, quando Claude riprende un subagent e ogni volta che un compagno 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 fanno sì che un nome con ambito del plugin venga trattato come espressione regolare, 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 inserire 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. Vedi 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 inserisce il contesto restituito solo se il contesto del subagent non contiene già la copia di un'esecuzione precedente. La copia inserita all'avvio resta al suo posto, lasciando intatta la prompt cache del subagent. Dopo che la compattazione automatica scarta quella copia, Claude Code inserisce 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 testo 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 avviato da Claude. Claude Code esegue anche agenti interni per alcune delle proprie funzionalità, come i suggerimenti di prompt e le domande laterali con /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 di esso.

Un matcher che specifica 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 successivo, un subagent eseguito con lo strumento SubagentHandback consegna il proprio report tramite quello strumento prima di arrestarsi. Il campo last_assistant_message contiene quindi l'eventuale testo conclusivo del subagent, che non è il report consegnato. Il report è 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 terminando con codice 2 consegna il proprio messaggio stderr allo stesso modo. Per inserire contesto nella sessione padre dopo che un subagent ha restituito il risultato, usa invece un hook PostToolUse sullo strumento Agent.

TaskCreated

Viene eseguito quando un task viene creato tramite lo strumento TaskCreate. Usalo per imporre convenzioni di denominazione, richiedere descrizioni dei task o impedire la creazione di determinati task. 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 del task in fase di creazione
task_subject Titolo del task
task_description Descrizione dettagliata del task. Può essere assente
teammate_name Nome del compagno di team che crea il task. 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 compagno di team in-process che crea il task. Può essere assente. Richiede Claude Code v2.1.290 o successivo

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 i task 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 task sta per essere contrassegnato come completato. Si attiva in due situazioni: quando un qualsiasi agente contrassegna esplicitamente un task come completato tramite lo strumento TaskUpdate, oppure quando un compagno di un team di agenti termina il proprio turno con task in corso. Usalo per imporre criteri di completamento, come il superamento dei test o dei controlli lint, prima che un task possa essere chiuso.

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 del task in fase di completamento
task_subject Titolo del task
task_description Descrizione dettagliata del task. Può essere assente
teammate_name Nome del compagno di team che completa il task. 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 compagno di team in-process che completa il task. Può essere assente. Richiede Claude Code v2.1.290 o successivo

Controllo delle decisioni di TaskCompleted

Gli hook TaskCompleted supportano due modi per controllare il completamento dei task:

  • Codice di uscita 2: il task non viene contrassegnato come completato e il messaggio stderr viene restituito al modello come feedback.
  • JSON {"continue": false, "stopReason": "..."}: quando l'evento è stato attivato da un compagno di team che termina il proprio turno, arresta completamente il compagno di 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 del task 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.

[!TIP]

Il comando /goal è una scorciatoia integrata per un hook Stop basato su prompt con ambito di sessione. Usalo quando vuoi che Claude continui a lavorare verso una condizione senza scrivere la configurazione dell'hook.

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à proseguendo a seguito di uno stop hook. 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 prosecuzioni consecutive: dopo che gli stop hook hanno fatto proseguire il turno otto volte di seguito, Claude Code sovrascrive il blocco successivo e termina il turno. Il conteggio delle prosecuzioni 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 testo 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 dei task è raggiungibile e sono vuoti quando non c'è nulla in corso o pianificato.

Ogni voce in background_tasks descrive un task in corso e usa questi campi:

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

Ogni voce in session_crons descrive una riattivazione pianificata con ambito di sessione, proveniente da CronCreate, ScheduleWakeup e /loop:

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

Questo esempio mostra un input di Stop con un task 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 prosegue. Oltre ai campi di output JSON disponibili per tutti gli hook, lo script del tuo hook può restituire questi campi specifici dell'evento:

Campo Descrizione
decision "block" impedisce a Claude di arrestarsi. Omettilo per consentire a Claude di arrestarsi
reason Obbligatorio quando decision è "block". Indica a Claude perché deve proseguire
hookSpecificOutput.additionalContext Feedback non di errore per Claude. La conversazione prosegue in modo che Claude possa 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 terminando con codice 2 viene instradato allo stesso modo di reason: Claude riceve il messaggio stderr come spiegazione del motivo per cui deve proseguire.

{
  "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 terminare". Mantiene attiva la conversazione con le stesse protezioni contro i cicli di decision: "block", ovvero l'input stop_hook_active e il limite di 8 prosecuzioni 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 registrazione nei log.

TeammateIdle

Viene eseguito quando un compagno di un team di agenti sta per diventare inattivo dopo aver terminato il proprio turno. Usalo per imporre controlli di qualità prima che un compagno di team smetta di lavorare, ad esempio richiedendo il superamento dei controlli lint o verificando l'esistenza dei file di output.

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 compagno di 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 compagno di team in-process che sta per diventare inattivo. Può essere assente. Richiede Claude Code v2.1.290 o successivo

Controllo delle decisioni di TeammateIdle

Gli hook TeammateIdle supportano due modi per controllare il comportamento del compagno di team:

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

Questo esempio verifica che esista un artefatto di build prima di consentire a un compagno di 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 verificare le modifiche alle impostazioni, applicare policy 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 policy gestita o un file di skill. Per la policy gestita, 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 alle policy del registro di Windows senza eseguirli. Su WSL con wslInheritsWindowsSettings, applica anche un file di impostazioni gestite lato Windows modificato durante il proprio polling delle policy, 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 la verifica della 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 tali 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 applica la 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 codice di 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. Se il tuo hook scrive variabili in quel file, queste persistono nei comandi Bash successivi fino al prossimo 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 osservati da FileChanged:

Campo Descrizione
watchPaths Array di percorsi assoluti. Sostituisce l'attuale elenco dinamico dei percorsi osservati. I percorsi della tua configurazione matcher vengono sempre osservati. Restituire un array vuoto cancella l'elenco dinamico, cosa tipica 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, o 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 queste directory
  • Aggiungi una directory nella scheda Workspace di /permissions
  • Aggiungi una directory che è già una directory di lavoro o si trova all'interno di una

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 fuori dalla 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 al modo in cui è 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 aggiunta
source Il modo in cui è 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à completata quando l'hook viene eseguito. Claude Code scarta il campo continue dal loro output JSON e gestisce il resto in modo diverso a seconda dell'origine:

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

FileChanged

Viene eseguito quando un file monitorato cambia sul disco. Claude Code rileva le modifiche con un watcher del filesystem, non esaminando 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:

  • Creare l'elenco dei file monitorati: il valore viene suddiviso in corrispondenza di | 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 del matcher sul basename del file modificato.

Questo esempio normalizza i fine riga in data.csv dopo qualsiasi modifica, incluso un comando Bash o uno script esterno che riscrive il file:

{
  "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 preciso 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 dei file monitorati. Claude Code avvia il watcher solo quando qualcosa indica 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 ometti il matcher nel gruppo che gestisce i percorsi dinamici: così corrisponde a ogni file monitorato e non aggiunge nulla all'elenco. Anche un matcher "*" corrisponde a ogni file, ma Claude Code lo registra nell'elenco dei file monitorati come qualsiasi altro valore, cioè come un file chiamato letteralmente *.

Gli hook FileChanged hanno accesso a CLAUDE_ENV_FILE. Le variabili scritte in quel file persistono nei comandi Bash successivi fino al prossimo 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 modificato
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'elenco dinamico corrente dei file monitorati. I percorsi dalla configurazione del tuo matcher vengono sempre monitorati. Usalo quando lo script del tuo hook individua file aggiuntivi da monitorare in base al file modificato

Gli hook FileChanged non hanno controllo decisionale. 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 systemMessage come una breve notifica nel terminale. Il messaggio non raggiunge lo stream di messaggi dell'SDK.

WorktreeCreate

Viene eseguito quando Claude Code crea un worktree in casi come questi:

Un hook WorktreeCreate non viene eseguito per un worktree che Claude aggiunge tramite Bash, e l'hook di un plugin non viene eseguito per quello creato quando avvii con --worktree e --tmux:

  • git worktree add tramite Bash: per ispezionare o bloccare quel comando, aggiungi un hook PreToolUse con il matcher Bash.
  • --worktree con --tmux: Claude Code crea il worktree prima di caricare i plugin, quindi un hook WorktreeCreate di un plugin non viene eseguito. Per far eseguire il tuo hook con entrambi i flag, definiscilo invece in un file di impostazioni. Prima della v2.1.300, l'hook WorktreeCreate di un plugin non veniva eseguito nemmeno con il solo --worktree.

Per impostazione predefinita Claude Code crea la copia di lavoro isolata con git worktree. Configurare un hook WorktreeCreate sostituisce quel comportamento predefinito di git, permettendoti di usare un diverso sistema di controllo versione 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 dello script del tuo 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 ogni tipo di hook restituisce il percorso.

Claude Code agisce in base all'esito positivo 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 deve 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 nell'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. L'esito dipende invece dal successo o dal fallimento dell'hook. L'hook deve restituire il percorso della directory del worktree creato:

  • Hook di tipo command (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 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 e termina con codice 1.

Claude Code rifiuta un percorso assoluto che contiene segmenti . o .., e qualsiasi percorso che attraversa un collegamento simbolico sotto la radice del repository. L'errore indica il componente rifiutato. Un collegamento simbolico incluso in un commit del repository potrebbe reindirizzare il worktree al di fuori di esso. Restituisci un percorso normalizzato che non attraversi un collegamento simbolico all'interno del repository.

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
  • Chiedi a Claude di uscire e rimuovere il worktree durante una sessione, e Claude lo rimuove con lo strumento ExitWorktree
  • Una sessione avviata da claude remote-control in modalità worktree termina senza arresti anomali, e Claude Code non trova file modificati o non tracciati né nuovi commit nel suo worktree
  • Arresti claude remote-control mentre le sessioni che ha avviato in modalità worktree sono ancora in esecuzione, e Claude Code non trova file modificati o non tracciati né nuovi commit nei loro worktree

Quando un subagent o un agente di un workflow termina, Claude Code mantiene il worktree che il tuo hook WorktreeCreate ha creato per esso e non esegue il tuo hook WorktreeRemove. Rimuovi tu stesso quei worktree quando il lavoro è finito.

Claude Code non ti chiede conferma prima che il tuo hook venga eseguito per una chiamata ExitWorktree o per una pulizia di claude remote-control. ExitWorktree si rifiuta di rimuovere un worktree creato da un hook a meno che la chiamata non passi discard_changes: true, il che rende il tuo hook l'ultimo controllo su quel percorso.

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. Verifica la presenza di quel lavoro nel tuo hook WorktreeRemove prima che elimini qualcosa.

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:

  • Nessun hook WorktreeRemove: Claude Code ripiega su git per una rimozione all'uscita o tramite ExitWorktree, e mantiene il worktree quando claude remote-control esegue la pulizia:
    • Esci da una sessione worktree, oppure Claude chiama ExitWorktree: Claude Code rimuove il percorso restituito dal tuo hook WorktreeCreate come farebbe git worktree remove --force, 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 diverso da git, rimane sul disco.
    • claude remote-control ripulisce il worktree di una sessione: il worktree rimane sul disco, e il terminale che esegue claude remote-control mostra worktree removal failed, kept: <path>.
    • Elimini una sessione in background: consulta Cosa rimuove l'eliminazione di una sessione.
  • L'hook esce con 0: il worktree è 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 dopo l'esecuzione, e il worktree rimane sul disco senza alcun fallback su git. Un hook che ha eliminato la directory prima di uscire con un codice diverso da zero è considerato come rimosso. Per sapere come viene segnalato l'errore, 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, rifiutando un percorso che è un collegamento simbolico o che ne attraversa uno sotto la radice del repository. In quel caso, l'hook viene eseguito per un worktree che contiene ancora file solo quando confermi l'eliminazione nella vista agenti. Per un worktree di questo tipo, claude rm mantiene invece la sessione e il worktree.

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 determina l'esito. Quando un hook esce con un codice diverso da zero e la directory in worktree_path esiste ancora dopo l'esecuzione, la rimozione fallisce:

  • Il worktree rimane sul 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 agenti riporta come è terminato l'hook, ad esempio exited 1, cita l'inizio del suo stderr e indica se eliminando di nuovo la sessione la directory viene comunque rimossa.
  • Se claude remote-control stava ripulendo il worktree di una sessione, il terminale che lo esegue mostra worktree removal failed, kept: <path>.

PreCompact

Viene eseguito prima che Claude Code esegua 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, e compattazione in caso di inattività

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

Bloccare la compattazione automatica ha effetti diversi a seconda di quando si attiva. Se la compattazione è stata attivata in modo preventivo prima del limite di contesto, Claude Code la salta e la conversazione continua senza essere compattata. 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
}

Output di PreCompact

Quando un hook PreCompact esce con 0 e non blocca, Claude Code usa il suo stdout come istruzioni per il riepilogo della compattazione. Mantieni quello stdout sotto i 10.000 caratteri per hook. Claude Code tronca l'output più lungo al limite e aggiunge un indicatore.

PostCompact

Viene eseguito dopo che Claude Code completa 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, e dopo la compattazione in caso di inattività

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 influenzare il 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 modelli 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 qualsiasi 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 a cui si risolvono, quindi claude-opus-5 copre ogni variante di scrittura di Opus 5.

Quando Claude Code non riesce a determinare un nome canonico per il modello di destinazione, ad esempio un ID di modello personalizzato che solo il tuo gateway LLM conosce, esegue ogni hook PreModelSwitch indipendentemente dal matcher. Un hook che blocca dovrebbe quindi controllare to_model dal suo input invece di 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 passaggio a Opus 4.6 uscendo con codice 2 e lascia passare qualsiasi altra destinazione:

[tab] macOS/Linux

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"
          }
        ]
      }
    ]
  }
}

[tab] Windows (PowerShell)

Registra un hook di tipo command che esegue uno script tramite PowerShell:

{
  "hooks": {
    "PreModelSwitch": [
      {
        "matcher": "claude-opus-4-6",
        "hooks": [
          {
            "type": "command",
            "command": "powershell.exe",
            "args": [
              "-NoProfile",
              "-ExecutionPolicy",
              "Bypass",
              "-File",
              "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-opus-46.ps1"
            ]
          }
        ]
      }
    ]
  }
}

Salva questo script in .claude/hooks/block-opus-46.ps1 nel tuo progetto:

$hookInput = [Console]::In.ReadToEnd() | ConvertFrom-Json
if ($hookInput.to_model -match 'opus-4-6') {
  [Console]::Error.WriteLine('Opus 4.6 is retired for this project. Use a newer model.')
  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 di 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 verso cui avviene il cambio. Il matcher si confronta 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 modelli; "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, di lettura dalla cache, di creazione della cache e di output dell'ultima risposta nella conversazione principale, sommati. 0 prima della prima risposta
prompt_cache_warm boolean Se la cache del prompt del modello corrente è probabilmente ancora calda, il che significa che il cambio la fa perdere
cache_ttl string Durata della cache del prompt della conversazione principale al momento di questo cambio: "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 assunto 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 in PreToolUse. PreModelSwitch accetta "allow", "deny" e "ask". Non accetta "defer", updatedInput o 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 per cui il cambio è stato bloccato, 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 di conferma di "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, l'ordine di 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.

Timeout di PreModelSwitch e altri codici di uscita

Un hook PreModelSwitch che non risponde prima del suo timeout blocca il cambio. Il timeout predefinito per questo evento è di 30 secondi.

Un codice di uscita diverso da 0 o 2 senza un output JSON valido è un errore non bloccante, quindi il cambio procede, tranne che per gli hook il cui fallimento lo blocca.

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 valida per tutta l'organizzazione che si applica a 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 una qualsiasi di queste modifiche:

  • 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
  • Il ripristino del modello da parte di Claude Code 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 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, poi chiedi a Claude quali indicazioni ha riguardo al modello corrente.

Input di PostModelSwitch

Gli hook PostModelSwitch ricevono gli stessi campi di PreModelSwitch, con hook_event_name impostato su "PostModelSwitch" e due valori aggiuntivi di source: "auto" per un fallback automatico o un'altra modifica effettuata autonomamente da Claude Code, 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 per salvare lo stato della sessione. Supporta i matcher per filtrare in base al motivo dell'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. Eliminalo 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 chiusura 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 singolo hook: imposta timeout nella configurazione di quell'hook. Il budget complessivo aumenta automaticamente per corrispondere al timeout per singolo 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 che imposti 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 un input all'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 del tutto la finestra di dialogo.

Per un hook completo con la relativa voce delle 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 facoltativi mode, url, elicitation_id e requested_schema.

Per l'elicitation in modalità form, 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 tramite 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 appare alcuna finestra di dialogo. Ogni riga di questa tabella mostra cosa restituire per un determinato 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à form 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 sia l'action che restituisci.

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 ha ignorato 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 appare la finestra di dialogo.

[tab] macOS/Linux

Registra un hook di tipo command 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

[tab] Windows (PowerShell)

Registra un hook di tipo command che esegue lo script tramite PowerShell, con il nome del server come matcher:

{
  "hooks": {
    "Elicitation": [
      {
        "matcher": "issue-tracker",
        "hooks": [
          {
            "type": "command",
            "command": "powershell.exe",
            "args": [
              "-NoProfile",
              "-ExecutionPolicy",
              "Bypass",
              "-File",
              "${CLAUDE_PROJECT_DIR}/.claude/hooks/answer-project-key.ps1"
            ]
          }
        ]
      }
    ]
  }
}

Salva questo script in .claude/hooks/answer-project-key.ps1 nel tuo progetto:

$request = [Console]::In.ReadToEnd() | ConvertFrom-Json
$fields = @($request.requested_schema.properties.PSObject.Properties.Name)

if ($fields.Count -eq 1 -and $fields[0] -eq 'project_key') {
  @{
    hookSpecificOutput = @{
      hookEventName = "Elicitation"
      action = "accept"
      content = @{ project_key = "DOCS" }
    }
  } | ConvertTo-Json -Depth 3
}

Per verificare che l'hook funzioni, avvia Claude Code con claude --debug e dai a Claude un'attività che porti il server a chiedere la chiave di progetto. Non appare 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 facoltativi 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 determinato 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 trasmetterli, 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 anche action, perché Claude Code ignora un hookSpecificOutput privo di 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 applica la stessa modifica quando l'utente ha accettato, mantiene gli altri campi e non stampa nulla negli altri casi:

#!/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 sia l'action che restituisci.

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 allo 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 invece il motivo a Claude come errore dello strumento in modo che possa adattarsi e continuare, equivalente a un hook di comando con permissionDecision: "deny"
  • 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

[!WARNING]

Gli hook agente sono sperimentali. Il comportamento e la configurazione potrebbero cambiare nelle versioni future. Per i flussi di lavoro in produzione, preferire gli hook di comando.

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, tranne che per l'output ripetuto di SessionStart. A differenza del systemMessage di un hook sincrono, nessuno dei due campi viene mostrato a te.

Stampa la risposta JSON da sola su stdout oppure su una riga a sé stante:

  • Da sola su stdout: quando la risposta è l'unico testo su stdout, può estendersi su più righe, come l'output formattato di jq. L'estensione su più righe richiede Claude Code v2.1.295 o versioni successive.
  • Su una riga a sé stante: un hook asincrono può stampare altro testo su stdout quando la risposta sta da sola su una riga, ad esempio con jq -c.

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. Esegui con --debug per vedere un avviso che nomina ogni campo scartato.

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.
  • Un hook SessionStart asincrono viene eseguito ogni volta che l'evento si attiva. Claude Code salta l'output dell'hook quando il testo di additionalContext e systemMessage corrisponde a quello che Claude ha ricevuto l'ultima volta da quell'hook, a meno che nel frattempo un hook SessionStart sincrono non abbia aggiunto altro testo. Claude Code consegna il testo quando cambia, e di nuovo la volta successiva in cui l'hook viene eseguito dopo una compattazione o /clear. Prima della v2.1.295, Claude Code aggiungeva di nuovo lo stesso testo alla conversazione a ogni ripresa.

Considerazioni sulla sicurezza

Disclaimer

[!WARNING]

I command hook eseguono comandi shell con i permessi completi dell'utente. Possono modificare, eliminare o accedere a qualsiasi file a cui l'account utente può accedere. Rivedere e testare tutti i comandi del hook prima di aggiungerli alla configurazione.

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'"
          }
        ]
      }
    ]
  }
}

Fare riferimento agli script tramite percorso negli hook PowerShell

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 trovare un hook lento, cerca nel log le righe Hooks: che terminano con una durata. In Claude Code v2.1.296 o versioni successive, ogni hook di comando su un evento di strumento, UserPromptSubmit, SessionStart, Stop e diversi altri eventi ne lascia una quando termina, indipendentemente da ciò che ha stampato. La riga riporta il nome dell'evento, unito con i due punti al nome dello strumento o a un altro valore su cui l'hook ha trovato corrispondenza, quindi il comando dell'hook tra parentesi quadre, il plugin da cui proviene se presente, come si è conclusa l'esecuzione e quanto tempo ha impiegato, come in Hooks: PostToolUse:Write [.claude/hooks/log-write.sh] finished with status 0 (31ms). Un'esecuzione può anche concludersi con timed out after <N>ms, cancelled, moved to the background o failed to start. Su alcuni eventi, come Notification, SessionEnd e PreCompact, un hook di comando lascia invece una riga completed with status senza durata.

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.