SpyBara
Go Premium

agent-sdk/hooks.md 2026-05-11 23:00 UTC to 2026-05-12 22:57 UTC

11 added, 11 removed.

2026
Sun 31 06:39 Sat 30 06:23 Fri 29 06:38 Thu 28 06:37 Wed 27 06:42 Tue 26 06:33 Sun 24 06:25 Sat 23 06:18 Fri 22 06:33 Thu 21 06:36 Wed 20 06:35 Tue 19 06:34 Mon 18 23:59 Sun 17 01:01 Fri 15 22:58 Thu 14 17:02 Wed 13 23:01 Tue 12 22:57 Mon 11 23:00 Sun 10 23:03 Sat 9 04:57 Fri 8 22:00 Thu 7 22:59 Tue 5 23:00 Mon 4 22:58 Sat 2 18:14 Fri 1 18:19

Intercettare e controllare il comportamento dell'agente con hooks

Intercettare e personalizzare il comportamento dell'agente nei punti chiave di esecuzione con hooks

Gli hooks sono funzioni di callback che eseguono il vostro codice in risposta agli eventi dell'agente, come una chiamata a uno strumento, l'avvio di una sessione o l'arresto dell'esecuzione. Con gli hooks, potete:

  • Bloccare operazioni pericolose prima che vengano eseguite, come comandi shell distruttivi o accesso a file non autorizzato
  • Registrare e controllare ogni chiamata a uno strumento per conformità, debug o analitiche
  • Trasformare input e output per sanitizzare i dati, iniettare credenziali o reindirizzare i percorsi dei file
  • Richiedere approvazione umana per azioni sensibili come scritture su database o chiamate API
  • Tracciare il ciclo di vita della sessione per gestire lo stato, pulire le risorse o inviare notifiche

Questa guida copre come funzionano gli hooks, come configurarli e fornisce esempi per modelli comuni come il blocco di strumenti, la modifica degli input e l'inoltro di notifiche.

Come funzionano gli hooks

1

Un evento si attiva

Qualcosa accade durante l'esecuzione dell'agente e l'SDK attiva un evento: uno strumento sta per essere chiamato (PreToolUse), uno strumento ha restituito un risultato (PostToolUse), un subagente è stato avviato o interrotto, l'agente è inattivo o l'esecuzione è terminata. Consultate l'elenco completo degli eventi.

2

L'SDK raccoglie gli hooks registrati

L'SDK verifica la presenza di hooks registrati per quel tipo di evento. Questo include gli hooks di callback che passate in options.hooks e gli hooks dei comandi shell dai file di impostazioni quando la voce settingSources o setting_sources corrispondente è abilitata, come avviene per le opzioni predefinite di query().

3

I matcher filtrano quali hooks vengono eseguiti

Se un hook ha un modello matcher (come "Write|Edit"), l'SDK lo testa rispetto al target dell'evento (ad esempio, il nome dello strumento). Gli hooks senza un matcher vengono eseguiti per ogni evento di quel tipo.

4

Le funzioni di callback vengono eseguite

Ogni hook corrispondente riceve la sua funzione di callback con input su ciò che sta accadendo: il nome dello strumento, i suoi argomenti, l'ID della sessione e altri dettagli specifici dell'evento.

5

Il vostro callback restituisce una decisione

Dopo aver eseguito qualsiasi operazione (registrazione, chiamate API, convalida), il vostro callback restituisce un oggetto di output che dice all'agente cosa fare: consentire l'operazione, bloccarla, modificare l'input o iniettare contesto nella conversazione.

L'esempio seguente mette insieme questi passaggi. Registra un hook PreToolUse (passaggio 1) con un matcher "Write|Edit" (passaggio 3) in modo che il callback si attivi solo per gli strumenti di scrittura di file. Quando attivato, il callback riceve l'input dello strumento (passaggio 4), verifica se il percorso del file è destinato a un file .env e restituisce permissionDecision: "deny" per bloccare l'operazione (passaggio 5):

import asyncio
from claude_agent_sdk import (
AssistantMessage,
ClaudeSDKClient,
ClaudeAgentOptions,
HookMatcher,
ResultMessage,
)


# Define a hook callback that receives tool call details
async def protect_env_files(input_data, tool_use_id, context):
# Extract the file path from the tool's input arguments
file_path = input_data["tool_input"].get("file_path", "")
file_name = file_path.split("/")[-1]

# Block the operation if targeting a .env file
if file_name == ".env":
return {
"hookSpecificOutput": {
"hookEventName": input_data["hook_event_name"],
"permissionDecision": "deny",
"permissionDecisionReason": "Cannot modify .env files",
}
}

# Return empty object to allow the operation
return {}


async def main():
options = ClaudeAgentOptions(
hooks={
# Register the hook for PreToolUse events
# The matcher filters to only Write and Edit tool calls
"PreToolUse": [HookMatcher(matcher="Write|Edit", hooks=[protect_env_files])]
}
)

async with ClaudeSDKClient(options=options) as client:
await client.query("Update the database configuration")
async for message in client.receive_response():
# Filter for assistant and result messages
if isinstance(message, (AssistantMessage, ResultMessage)):
print(message)


asyncio.run(main())

Hook disponibili

L'SDK fornisce hooks per diverse fasi dell'esecuzione dell'agente. Alcuni hooks sono disponibili in entrambi gli SDK, mentre altri sono solo per TypeScript.

Hook Event Python SDK TypeScript SDK Cosa lo attiva Caso d'uso di esempio
PreToolUse Richiesta di chiamata a uno strumento (può bloccare o modificare) Bloccare comandi shell pericolosi
PostToolUse Risultato dell'esecuzione dello strumento Registrare tutte le modifiche ai file nel registro di controllo
PostToolUseFailure Errore di esecuzione dello strumento Gestire o registrare errori dello strumento
PostToolBatch No Un intero batch di chiamate a strumenti si risolve, una volta per batch prima della prossima chiamata al modello Iniettare convenzioni una volta per l'intero batch
UserPromptSubmit Invio del prompt dell'utente Iniettare contesto aggiuntivo nei prompt
Stop Arresto dell'esecuzione dell'agente Salvare lo stato della sessione prima dell'uscita
SubagentStart Inizializzazione del subagente Tracciare l'avvio di attività parallele
SubagentStop Completamento del subagente Aggregare i risultati dalle attività parallele
PreCompact Richiesta di compattazione della conversazione Archiviare la trascrizione completa prima del riepilogo
PermissionRequest La finestra di dialogo delle autorizzazioni verrebbe visualizzata Gestione personalizzata delle autorizzazioni
SessionStart No Inizializzazione della sessione Inizializzare la registrazione e la telemetria
SessionEnd No Terminazione della sessione Pulire le risorse temporanee
Notification Messaggi di stato dell'agente Inviare aggiornamenti dello stato dell'agente a Slack o PagerDuty
Setup No Configurazione/manutenzione della sessione Eseguire attività di inizializzazione
TeammateIdle No Il compagno di squadra diventa inattivo Riassegnare il lavoro o notificare
TaskCompleted No L'attività in background si completa Aggregare i risultati dalle attività parallele
ConfigChange No Il file di configurazione cambia Ricaricare le impostazioni dinamicamente
WorktreeCreate No Git worktree creato Tracciare gli spazi di lavoro isolati
WorktreeRemove No Git worktree rimosso Pulire le risorse dello spazio di lavoro

Configurare gli hooks

Per configurare un hook, passatelo nel campo hooks delle opzioni dell'agente (ClaudeAgentOptions in Python, l'oggetto options in TypeScript):

options = ClaudeAgentOptions(
hooks={"PreToolUse": [HookMatcher(matcher="Bash", hooks=[my_callback])]}
)

async with ClaudeSDKClient(options=options) as client:
await client.query("Your prompt")
async for message in client.receive_response():
print(message)

L'opzione hooks è un dizionario (Python) o un oggetto (TypeScript) dove:

Matcher

Utilizzate i matcher per filtrare quando i vostri callback si attivano. Il campo matcher è una stringa regex che corrisponde a un valore diverso a seconda del tipo di evento hook. Ad esempio, gli hook basati su strumenti corrispondono al nome dello strumento, mentre gli hook Notification corrispondono al tipo di notifica. Consultate il riferimento degli hooks di Claude Code per l'elenco completo dei valori di matcher per ogni tipo di evento.

Opzione Tipo Predefinito Descrizione
matcher string undefined Modello regex abbinato al campo di filtro dell'evento. Per gli hook degli strumenti, questo è il nome dello strumento. Gli strumenti incorporati includono Bash, Read, Write, Edit, Glob, Grep, WebFetch, Agent e altri (consultate Tipi di input degli strumenti per l'elenco completo). Gli strumenti MCP utilizzano il modello mcp__<server>__<action>.
hooks HookCallback[] - Obbligatorio. Array di funzioni di callback da eseguire quando il modello corrisponde
timeout number 60 Timeout in secondi

Utilizzate il modello matcher per indirizzare strumenti specifici quando possibile. Un matcher con 'Bash' viene eseguito solo per i comandi Bash, mentre omettere il modello esegue i vostri callback per ogni occorrenza dell'evento. Notate che per gli hook basati su strumenti, i matcher filtrano solo per nome dello strumento, non per percorsi di file o altri argomenti. Per filtrare per percorso di file, controllate tool_input.file_path all'interno del vostro callback.

Funzioni di callback

Input

Ogni callback hook riceve tre argomenti:

  • Dati di input: un oggetto tipizzato contenente i dettagli dell'evento. Ogni tipo di hook ha la sua forma di input (ad esempio, PreToolUseHookInput include tool_name e tool_input, mentre NotificationHookInput include message). Consultate le definizioni di tipo complete nei riferimenti SDK TypeScript e Python.
    • Tutti gli input hook condividono session_id, cwd e hook_event_name.
    • agent_id e agent_type vengono popolati quando l'hook si attiva all'interno di un subagente. In TypeScript, questi si trovano sull'input hook di base e sono disponibili per tutti i tipi di hook. In Python, si trovano solo su PreToolUse, PostToolUse e PostToolUseFailure.
  • ID di utilizzo dello strumento (str | None / string | undefined): correla gli eventi PreToolUse e PostToolUse per la stessa chiamata a uno strumento.
  • Contesto: in TypeScript, contiene una proprietà signal (AbortSignal) per l'annullamento. In Python, questo argomento è riservato per uso futuro.

Output

Il vostro callback restituisce un oggetto con due categorie di campi:

  • Campi di livello superiore controllano la conversazione: systemMessage inietta un messaggio nella conversazione visibile al modello e continue (continue_ in Python) determina se l'agente continua a funzionare dopo questo hook.
  • hookSpecificOutput controlla l'operazione corrente. I campi all'interno dipendono dal tipo di evento hook. Per gli hook PreToolUse, è qui che impostate permissionDecision ("allow", "deny", "ask" o "defer"), permissionDecisionReason e updatedInput. Restituire "defer" termina la query in modo da poter riprendere in seguito. Per gli hook PostToolUse, potete impostare additionalContext per aggiungere informazioni al risultato dello strumento, o updatedToolOutput per sostituire completamente l'output dello strumento prima che Claude lo veda.

Restituite {} per consentire l'operazione senza modifiche. Gli hook di callback SDK utilizzano lo stesso formato di output JSON degli hook dei comandi shell di Claude Code, che documenta ogni campo e opzione specifica dell'evento. Per le definizioni di tipo SDK, consultate i riferimenti SDK TypeScript e Python.

Output asincrono

Per impostazione predefinita, l'agente attende che il vostro hook restituisca prima di procedere. Se il vostro hook esegue un effetto collaterale (registrazione, invio di un webhook) e non ha bisogno di influenzare il comportamento dell'agente, potete restituire un output asincrono. Questo dice all'agente di continuare immediatamente senza attendere il completamento dell'hook:

async def async_hook(input_data, tool_use_id, context):
# Start a background task, then return immediately
asyncio.create_task(send_to_logging_service(input_data))
return {"async_": True, "asyncTimeout": 30000}
Campo Tipo Descrizione
async true Segnala la modalità asincrona. L'agente procede senza attendere. In Python, utilizzate async_ per evitare la parola chiave riservata.
asyncTimeout number Timeout opzionale in millisecondi per l'operazione in background

Esempi

Modificare l'input dello strumento

Questo esempio intercetta le chiamate allo strumento Write e riscrive l'argomento file_path per anteporre /sandbox, reindirizzando tutte le scritture di file a una directory sandbox. Il callback restituisce updatedInput con il percorso modificato e permissionDecision: 'allow' per approvare automaticamente l'operazione riscritta:

async def redirect_to_sandbox(input_data, tool_use_id, context):
if input_data["hook_event_name"] != "PreToolUse":
return {}

if input_data["tool_name"] == "Write":
original_path = input_data["tool_input"].get("file_path", "")
return {
"hookSpecificOutput": {
"hookEventName": input_data["hook_event_name"],
"permissionDecision": "allow",
"updatedInput": {
**input_data["tool_input"],
"file_path": f"/sandbox{original_path}",
},
}
}
return {}

Aggiungere contesto e bloccare uno strumento

Questo esempio blocca qualsiasi tentativo di scrivere nella directory /etc e utilizza due campi di output insieme: permissionDecision: 'deny' interrompe la chiamata dello strumento, mentre systemMessage inietta un promemoria nella conversazione in modo che l'agente riceva il contesto sul motivo per cui l'operazione è stata bloccata e eviti di ritentarla:

async def block_etc_writes(input_data, tool_use_id, context):
file_path = input_data["tool_input"].get("file_path", "")

if file_path.startswith("/etc"):
return {
# Top-level field: inject guidance into the conversation
"systemMessage": "Remember: system directories like /etc are protected.",
# hookSpecificOutput: block the operation
"hookSpecificOutput": {
"hookEventName": input_data["hook_event_name"],
"permissionDecision": "deny",
"permissionDecisionReason": "Writing to /etc is not allowed",
},
}
return {}

Approvare automaticamente strumenti specifici

Per impostazione predefinita, l'agente potrebbe richiedere l'autorizzazione prima di utilizzare determinati strumenti. Questo esempio approva automaticamente gli strumenti del file system di sola lettura (Read, Glob, Grep) restituendo permissionDecision: 'allow', consentendo loro di funzionare senza conferma dell'utente mentre lascia tutti gli altri strumenti soggetti ai normali controlli di autorizzazione:

async def auto_approve_read_only(input_data, tool_use_id, context):
if input_data["hook_event_name"] != "PreToolUse":
return {}

read_only_tools = ["Read", "Glob", "Grep"]
if input_data["tool_name"] in read_only_tools:
return {
"hookSpecificOutput": {
"hookEventName": input_data["hook_event_name"],
"permissionDecision": "allow",
"permissionDecisionReason": "Read-only tool auto-approved",
}
}
return {}

Registrare più hook

Quando un evento si attiva, tutti gli hook corrispondenti vengono eseguiti in parallelo. Per le decisioni di autorizzazione, il risultato più restrittivo vince: un singolo deny blocca la chiamata dello strumento indipendentemente da ciò che gli altri hook restituiscono. Poiché l'ordine di completamento è non deterministico, scrivete ogni hook per agire in modo indipendente piuttosto che fare affidamento su un altro hook che sia stato eseguito per primo.

L'esempio seguente registra tre controlli indipendenti per ogni chiamata a uno strumento:

options = ClaudeAgentOptions(
hooks={
"PreToolUse": [
HookMatcher(hooks=[authorization_check]),
HookMatcher(hooks=[input_validator]),
HookMatcher(hooks=[audit_logger]),
]
}
)

Filtrare con matcher regex

Utilizzate modelli regex per abbinare più strumenti. Questo esempio registra tre matcher con ambiti diversi: il primo attiva file_security_hook solo per gli strumenti di modifica dei file, il secondo attiva mcp_audit_hook per qualsiasi strumento MCP (strumenti i cui nomi iniziano con mcp__), e il terzo attiva global_logger per ogni chiamata a uno strumento indipendentemente dal nome:

options = ClaudeAgentOptions(
hooks={
"PreToolUse": [
# Match file modification tools
HookMatcher(matcher="Write|Edit|Delete", hooks=[file_security_hook]),
# Match all MCP tools
HookMatcher(matcher="^mcp__", hooks=[mcp_audit_hook]),
# Match everything (no matcher)
HookMatcher(hooks=[global_logger]),
]
}
)

Tracciare l'attività dei subagenti

Utilizzate gli hook SubagentStop per monitorare quando i subagenti completano il loro lavoro. Consultate il tipo di input completo nei riferimenti SDK TypeScript e Python. Questo esempio registra un riepilogo ogni volta che un subagente si completa:

async def subagent_tracker(input_data, tool_use_id, context):
# Log subagent details when it finishes
print(f"[SUBAGENT] Completed: {input_data['agent_id']}")
print(f"  Transcript: {input_data['agent_transcript_path']}")
print(f"  Tool use ID: {tool_use_id}")
print(f"  Stop hook active: {input_data.get('stop_hook_active')}")
return {}


options = ClaudeAgentOptions(
hooks={"SubagentStop": [HookMatcher(hooks=[subagent_tracker])]}
)

Effettuare richieste HTTP dagli hooks

Gli hooks possono eseguire operazioni asincrone come richieste HTTP. Catturate gli errori all'interno del vostro hook invece di lasciarli propagare, poiché un'eccezione non gestita può interrompere l'agente.

Questo esempio invia un webhook dopo il completamento di ogni strumento, registrando quale strumento è stato eseguito e quando. L'hook cattura gli errori in modo che un webhook non riuscito non interrompa l'agente:

import asyncio
import json
import urllib.request
from datetime import datetime


def _send_webhook(tool_name):
"""Synchronous helper that POSTs tool usage data to an external webhook."""
data = json.dumps(
{
"tool": tool_name,
"timestamp": datetime.now().isoformat(),
}
).encode()
req = urllib.request.Request(
"https://api.example.com/webhook",
data=data,
headers={"Content-Type": "application/json"},
method="POST",
)
urllib.request.urlopen(req)


async def webhook_notifier(input_data, tool_use_id, context):
# Only fire after a tool completes (PostToolUse), not before
if input_data["hook_event_name"] != "PostToolUse":
return {}

try:
# Run the blocking HTTP call in a thread to avoid blocking the event loop
await asyncio.to_thread(_send_webhook, input_data["tool_name"])
except Exception as e:
# Log the error but don't raise. A failed webhook shouldn't stop the agent
print(f"Webhook request failed: {e}")

return {}

Inoltrare le notifiche a Slack

Utilizzate gli hook Notification per ricevere notifiche di sistema dall'agente e inoltrarle a servizi esterni. Le notifiche si attivano per tipi di evento specifici: permission_prompt (Claude ha bisogno di autorizzazione), idle_prompt (Claude è in attesa di input), auth_success (autenticazione completata), elicitation_dialog (Claude sta richiedendo all'utente), elicitation_response (l'utente ha risposto a un'elicitazione) e elicitation_complete (un'elicitazione è stata chiusa). Ogni notifica include un campo message con una descrizione leggibile dall'uomo e facoltativamente un title.

Questo esempio inoltra ogni notifica a un canale Slack. Richiede un URL webhook in arrivo di Slack, che create aggiungendo un'app al vostro spazio di lavoro Slack e abilitando i webhook in arrivo:

import asyncio
import json
import urllib.request

from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, HookMatcher


def _send_slack_notification(message):
"""Synchronous helper that sends a message to Slack via incoming webhook."""
data = json.dumps({"text": f"Agent status: {message}"}).encode()
req = urllib.request.Request(
"https://hooks.slack.com/services/YOUR/WEBHOOK/URL",
data=data,
headers={"Content-Type": "application/json"},
method="POST",
)
urllib.request.urlopen(req)


async def notification_handler(input_data, tool_use_id, context):
try:
# Run the blocking HTTP call in a thread to avoid blocking the event loop
await asyncio.to_thread(_send_slack_notification, input_data.get("message", ""))
except Exception as e:
print(f"Failed to send notification: {e}")

# Return empty object. Notification hooks don't modify agent behavior
return {}


async def main():
options = ClaudeAgentOptions(
hooks={
# Register the hook for Notification events (no matcher needed)
"Notification": [HookMatcher(hooks=[notification_handler])],
},
)

async with ClaudeSDKClient(options=options) as client:
await client.query("Analyze this codebase")
async for message in client.receive_response():
print(message)


asyncio.run(main())

Risolvere i problemi comuni

Hook non si attiva

  • Verificate che il nome dell'evento hook sia corretto e sensibile alle maiuscole (PreToolUse, non preToolUse)
  • Controllate che il vostro modello di matcher corrisponda esattamente al nome dello strumento
  • Assicuratevi che l'hook sia sotto il tipo di evento corretto in options.hooks
  • Per gli hook non basati su strumenti come Stop e SubagentStop, i matcher corrispondono a campi diversi (consultate modelli di matcher)
  • Gli hooks potrebbero non attivarsi quando l'agente raggiunge il limite max_turns perché la sessione termina prima che gli hooks possano essere eseguiti

Matcher non filtra come previsto

I matcher corrispondono solo ai nomi degli strumenti, non ai percorsi dei file o ad altri argomenti. Per filtrare per percorso di file, controllate tool_input.file_path all'interno del vostro hook:

const myHook: HookCallback = async (input, toolUseID, { signal }) => {
  const preInput = input as PreToolUseHookInput;
  const toolInput = preInput.tool_input as Record<string, unknown>;
  const filePath = toolInput?.file_path as string;
  if (!filePath?.endsWith(".md")) return {}; // Skip non-markdown files
  // Process markdown files...
  return {};
};

Timeout dell'hook

  • Aumentate il valore timeout nella configurazione HookMatcher
  • Utilizzate AbortSignal dal terzo argomento del callback per gestire l'annullamento con eleganza in TypeScript

Strumento bloccato inaspettatamente

  • Controllate tutti gli hook PreToolUse per i ritorni permissionDecision: 'deny'
  • Aggiungete la registrazione ai vostri hook per vedere quale permissionDecisionReason stanno restituendo
  • Verificate che i modelli di matcher non siano troppo ampi (un matcher vuoto corrisponde a tutti gli strumenti)

Input modificato non applicato

  • Assicuratevi che updatedInput sia all'interno di hookSpecificOutput, non al livello superiore:

    return {
      hookSpecificOutput: {
        hookEventName: "PreToolUse",
        permissionDecision: "allow",
        updatedInput: { command: "new command" }
      }
    };
    
  • Dovete anche restituire permissionDecision: 'allow' o 'ask' affinché la modifica dell'input abbia effetto

  • Includete hookEventName in hookSpecificOutput per identificare quale tipo di hook è l'output

Hook di sessione non disponibili in Python

SessionStart e SessionEnd possono essere registrati come hook di callback SDK in TypeScript, ma non sono disponibili nell'SDK Python (HookEvent li omette). In Python, sono disponibili solo come hook dei comandi shell definiti nei file di impostazioni (ad esempio, .claude/settings.json). Per caricare gli hook dei comandi shell dalla vostra applicazione SDK, includete la fonte di impostazione appropriata con setting_sources o settingSources:

options = ClaudeAgentOptions(
setting_sources=["project"],  # Loads .claude/settings.json including hooks
)

Per eseguire la logica di inizializzazione come callback SDK Python, utilizzate il primo messaggio da client.receive_response() come trigger.

I prompt di autorizzazione dei subagenti si moltiplicano

Quando si avviano più subagenti, ognuno potrebbe richiedere autorizzazioni separatamente. I subagenti non ereditano automaticamente le autorizzazioni dell'agente genitore. Per evitare prompt ripetuti, utilizzate gli hook PreToolUse per approvare automaticamente strumenti specifici o configurate regole di autorizzazione che si applicano alle sessioni dei subagenti.

Loop ricorsivi di hook con subagenti

Un hook UserPromptSubmit che avvia subagenti può creare loop infiniti se quei subagenti attivano lo stesso hook. Per prevenire questo:

  • Controllate un indicatore di subagente nell'input dell'hook prima di avviare
  • Utilizzate una variabile condivisa o lo stato della sessione per tracciare se siete già all'interno di un subagente
  • Limitate gli hook per l'esecuzione solo per la sessione dell'agente di livello superiore

systemMessage non appare nell'output

Il campo systemMessage aggiunge contesto alla conversazione che il modello vede, ma potrebbe non apparire in tutte le modalità di output SDK. Se avete bisogno di far emergere le decisioni degli hook alla vostra applicazione, registratele separatamente o utilizzate un canale di output dedicato.

Risorse correlate