Hooks ermöglichen es dir, die Agentausführung an wichtigen Punkten abzufangen, um Validierung, Protokollierung, Sicherheitskontrollen oder benutzerdefinierte Logik hinzuzufügen. Mit Hooks kannst du:
Ein Hook hat zwei Teile:
PreToolUse) und welche Tools übereinstimmen sollenDas folgende Beispiel blockiert den Agent daran, .env-Dateien zu ändern. Definiere zunächst einen Callback, der den Dateipfad überprüft, und übergebe ihn dann an query(), um ihn vor jedem Write- oder Edit-Tool-Aufruf auszuführen:
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher
# 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():
async for message in query(
prompt="Update the database configuration",
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])]
}
)
):
print(message)
asyncio.run(main())Dies ist ein PreToolUse-Hook. Er wird vor der Werkzeugausführung ausgeführt und kann Operationen basierend auf deiner Logik blockieren oder zulassen. Der Rest dieses Leitfadens behandelt alle verfügbaren Hooks, ihre Konfigurationsoptionen und Muster für häufige Anwendungsfälle.
Das SDK bietet Hooks für verschiedene Phasen der Agentausführung. Einige Hooks sind in beiden SDKs verfügbar, während andere nur in TypeScript verfügbar sind, da das Python SDK sie nicht unterstützt.
| Hook-Ereignis | Python SDK | TypeScript SDK | Was löst es aus | Beispiel-Anwendungsfall |
|---|---|---|---|---|
PreToolUse | Ja | Ja | Tool-Aufrufanforderung (kann blockiert oder geändert werden) | Blockiere gefährliche Shell-Befehle |
PostToolUse | Ja | Ja | Werkzeugausführungsergebnis | Protokolliere alle Dateiänderungen im Audit-Trail |
PostToolUseFailure | Nein | Ja | Werkzeugausführungsfehler | Behandle oder protokolliere Werkzeugfehler |
UserPromptSubmit | Ja | Ja | Benutzer-Prompt-Einreichung | Injiziere zusätzlichen Kontext in Prompts |
Stop | Ja | Ja | Agent-Ausführungsstopp | Speichere den Sitzungsstatus vor dem Beenden |
SubagentStart | Nein | Ja | Subagent-Initialisierung | Verfolge parallele Task-Spawning |
SubagentStop | Ja | Ja | Subagent-Abschluss | Aggregiere Ergebnisse aus parallelen Tasks |
PreCompact | Ja | Ja | Anforderung zur Gesprächskomprimierung | Archiviere vollständiges Transkript vor der Zusammenfassung |
PermissionRequest | Nein | Ja | Genehmigungsdialog würde angezeigt | Benutzerdefinierte Genehmigungsbehandlung |
SessionStart | Nein | Ja | Sitzungsinitialisierung | Initialisiere Protokollierung und Telemetrie |
SessionEnd | Nein | Ja | Sitzungsbeendigung | Bereinige temporäre Ressourcen |
Notification | Nein | Ja | Agent-Statusmeldungen | Sende Agent-Statusaktualisierungen an Slack oder PagerDuty |
Hooks sind flexibel genug, um viele verschiedene Szenarien zu handhaben. Hier sind einige der häufigsten Muster, organisiert nach Kategorie.
Um einen Hook für deinen Agent zu konfigurieren, übergebe den Hook im Parameter options.hooks beim Aufrufen von query():
async for message in query(
prompt="Your prompt",
options=ClaudeAgentOptions(
hooks={
'PreToolUse': [HookMatcher(matcher='Bash', hooks=[my_callback])]
}
)
):
print(message)Die hooks-Option ist ein Wörterbuch (Python) oder Objekt (TypeScript), wobei:
'PreToolUse', 'PostToolUse', 'Stop')Deine Hook-Callback-Funktionen erhalten Eingabedaten über das Ereignis und geben eine Antwort zurück, damit der Agent weiß, ob er die Operation zulassen, blockieren oder ändern soll.
Verwende Matcher, um zu filtern, welche Tools deine Callbacks auslösen:
| Option | Typ | Standard | Beschreibung |
|---|---|---|---|
matcher | string | undefined | Regex-Muster zum Abgleich von Tool-Namen. Integrierte Tools umfassen Bash, Read, Write, Edit, Glob, Grep, WebFetch, Task und andere. MCP-Tools verwenden das Muster mcp__<server>__<action>. |
hooks | HookCallback[] | - | Erforderlich. Array von Callback-Funktionen, die ausgeführt werden, wenn das Muster übereinstimmt |
timeout | number | 60 | Timeout in Sekunden; erhöhe für Hooks, die externe API-Aufrufe tätigen |
Verwende das matcher-Muster, um nach Möglichkeit spezifische Tools anzusteuern. Ein Matcher mit 'Bash' wird nur für Bash-Befehle ausgeführt, während das Weglassen des Musters deine Callbacks für jeden Tool-Aufruf ausführt. Beachte, dass Matcher nur nach Tool-Namen filtern, nicht nach Dateipfaden oder anderen Argumenten – um nach Dateipfad zu filtern, überprüfe tool_input.file_path in deinem Callback.
Matcher gelten nur für Tool-basierte Hooks (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest). Für Lifecycle-Hooks wie Stop, SessionStart und Notification werden Matcher ignoriert und der Hook wird für alle Ereignisse dieses Typs ausgelöst.
Tool-Namen entdecken: Überprüfe das tools-Array in der anfänglichen Systemmeldung, wenn deine Sitzung startet, oder füge einen Hook ohne Matcher hinzu, um alle Tool-Aufrufe zu protokollieren.
MCP-Tool-Benennung: MCP-Tools beginnen immer mit mcp__ gefolgt vom Servernamen und der Aktion: mcp__<server>__<action>. Wenn du beispielsweise einen Server namens playwright konfigurierst, werden seine Tools mcp__playwright__browser_screenshot, mcp__playwright__browser_click usw. benannt. Der Servername stammt aus dem Schlüssel, den du in der mcpServers-Konfiguration verwendest.
Dieses Beispiel verwendet einen Matcher, um einen Hook nur für dateimodifizierende Tools auszuführen, wenn das PreToolUse-Ereignis ausgelöst wird:
options = ClaudeAgentOptions(
hooks={
'PreToolUse': [
HookMatcher(matcher='Write|Edit', hooks=[validate_file_path])
]
}
)Jeder Hook-Callback erhält drei Argumente:
dict / HookInput): Ereignisdetails. Siehe Eingabedaten für Felderstr | None / string | null): Korreliere PreToolUse- und PostToolUse-EreignisseHookContext): In TypeScript enthält eine signal-Eigenschaft (AbortSignal) zur Stornierung. Übergebe dies an asynchrone Operationen wie fetch(), damit sie automatisch storniert werden, wenn der Hook das Timeout überschreitet. In Python ist dieses Argument für zukünftige Verwendung reserviert.Das erste Argument für deinen Hook-Callback enthält Informationen über das Ereignis. Feldnamen sind über SDKs identisch (beide verwenden snake_case).
Gemeinsame Felder in allen Hook-Typen:
| Feld | Typ | Beschreibung |
|---|---|---|
hook_event_name | string | Der Hook-Typ (PreToolUse, PostToolUse usw.) |
session_id | string | Aktuelle Sitzungskennung |
transcript_path | string | Pfad zum Gesprächstranskript |
cwd | string | Aktuelles Arbeitsverzeichnis |
Hook-spezifische Felder variieren je nach Hook-Typ. Elemente, die mit TS gekennzeichnet sind, sind nur im TypeScript SDK verfügbar:
| Feld | Typ | Beschreibung | Hooks |
|---|---|---|---|
tool_name | string | Name des aufgerufenen Tools | PreToolUse, PostToolUse, PostToolUseFailureTS, PermissionRequestTS |
tool_input | object | An das Tool übergebene Argumente | PreToolUse, PostToolUse, PostToolUseFailureTS, PermissionRequestTS |
tool_response | any | Von der Werkzeugausführung zurückgegebenes Ergebnis | PostToolUse |
error | string | Fehlermeldung aus Werkzeugausführungsfehler | PostToolUseFailureTS |
is_interrupt | boolean | Ob der Fehler durch eine Unterbrechung verursacht wurde | PostToolUseFailureTS |
prompt | string | Der Text des Benutzer-Prompts | UserPromptSubmit |
stop_hook_active | boolean | Ob ein Stop-Hook gerade verarbeitet wird | Stop, SubagentStop |
agent_id | string | Eindeutige Kennung für den Subagent | SubagentStartTS, SubagentStopTS |
agent_type | string | Typ/Rolle des Subagents | SubagentStartTS |
agent_transcript_path | string | Pfad zum Gesprächstranskript des Subagents | SubagentStopTS |
trigger | string | Was die Komprimierung ausgelöst hat: manual oder auto | PreCompact |
custom_instructions | string | Benutzerdefinierte Anweisungen für die Komprimierung | PreCompact |
permission_suggestions | array | Vorgeschlagene Genehmigungsaktualisierungen für das Tool | PermissionRequestTS |
source | string | Wie die Sitzung gestartet wurde: startup, resume, clear oder compact | SessionStartTS |
reason | string | Warum die Sitzung endete: clear, logout, prompt_input_exit, bypass_permissions_disabled oder other | SessionEndTS |
message | string | Statusmeldung vom Agent | NotificationTS |
notification_type | string | Art der Benachrichtigung: permission_prompt, idle_prompt, auth_success oder elicitation_dialog | NotificationTS |
title | string | Optionaler vom Agent festgelegter Titel | NotificationTS |
Der folgende Code definiert einen Hook-Callback, der tool_name und tool_input verwendet, um Details zu jedem Tool-Aufruf zu protokollieren:
async def log_tool_calls(input_data, tool_use_id, context):
if input_data['hook_event_name'] == 'PreToolUse':
print(f"Tool: {input_data['tool_name']}")
print(f"Input: {input_data['tool_input']}")
return {}Deine Callback-Funktion gibt ein Objekt zurück, das dem SDK mitteilt, wie es fortfahren soll. Gib ein leeres Objekt {} zurück, um die Operation ohne Änderungen zuzulassen. Um die Operation zu blockieren, zu ändern oder Kontext hinzuzufügen, gib ein Objekt mit einem hookSpecificOutput-Feld zurück, das deine Entscheidung enthält.
Top-Level-Felder (außerhalb von hookSpecificOutput):
| Feld | Typ | Beschreibung |
|---|---|---|
continue | boolean | Ob der Agent nach diesem Hook fortfahren soll (Standard: true) |
stopReason | string | Meldung, die angezeigt wird, wenn continue false ist |
suppressOutput | boolean | Verstecke stdout aus dem Transkript (Standard: false) |
systemMessage | string | Meldung, die in das Gespräch für Claude eingefügt wird |
Felder innerhalb von hookSpecificOutput:
| Feld | Typ | Hooks | Beschreibung |
|---|---|---|---|
hookEventName | string | Alle | Erforderlich. Verwende input.hook_event_name, um das aktuelle Ereignis abzugleichen |
permissionDecision | 'allow' | 'deny' | 'ask' | PreToolUse | Steuert, ob das Tool ausgeführt wird |
permissionDecisionReason | string | PreToolUse | Erklärung, die Claude für die Entscheidung angezeigt wird |
updatedInput | object | PreToolUse | Geänderte Tool-Eingabe (erfordert permissionDecision: 'allow') |
additionalContext | string | PreToolUse, PostToolUse, UserPromptSubmit, SessionStartTS, SubagentStartTS | Kontext, der zum Gespräch hinzugefügt wird |
Dieses Beispiel blockiert Schreibvorgänge in das /etc-Verzeichnis und injiziert eine Systemmeldung, um Claude an sichere Dateipraktiken zu erinnern:
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 {}Wenn mehrere Hooks oder Genehmigungsregeln gelten, wertet das SDK sie in dieser Reihenfolge aus:
Wenn ein Hook deny zurückgibt, wird die Operation blockiert – andere Hooks, die allow zurückgeben, können dies nicht überschreiben.
Gib eine Deny-Entscheidung zurück, um die Werkzeugausführung zu verhindern:
async def block_dangerous_commands(input_data, tool_use_id, context):
if input_data['hook_event_name'] != 'PreToolUse':
return {}
command = input_data['tool_input'].get('command', '')
if 'rm -rf /' in command:
return {
'hookSpecificOutput': {
'hookEventName': input_data['hook_event_name'],
'permissionDecision': 'deny',
'permissionDecisionReason': 'Dangerous command blocked: rm -rf /'
}
}
return {}Gib aktualisierte Eingabe zurück, um zu ändern, was das Tool erhält:
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 {}Wenn du updatedInput verwendest, musst du auch permissionDecision einschließen. Gib immer ein neues Objekt zurück, anstatt das ursprüngliche tool_input zu mutieren.
Injiziere Kontext in das Gespräch:
async def add_security_reminder(input_data, tool_use_id, context):
return {
'systemMessage': 'Remember to follow security best practices.'
}Umgehe Genehmigungsaufforderungen für vertrauenswürdige Tools. Dies ist nützlich, wenn du möchtest, dass bestimmte Operationen ohne Benutzerbestätigung ausgeführt werden:
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', 'LS']
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 {}Das permissionDecision-Feld akzeptiert drei Werte: 'allow' (automatisch genehmigen), 'deny' (blockieren) oder 'ask' (zur Bestätigung auffordern).
Diese Muster helfen dir, komplexere Hook-Systeme für komplexe Anwendungsfälle zu erstellen.
Hooks werden in der Reihenfolge ausgeführt, in der sie im Array angezeigt werden. Halte jeden Hook auf eine einzelne Verantwortung konzentriert und verkette mehrere Hooks für komplexe Logik. Dieses Beispiel führt alle vier Hooks für jeden Tool-Aufruf aus (kein Matcher angegeben):
options = ClaudeAgentOptions(
hooks={
'PreToolUse': [
HookMatcher(hooks=[rate_limiter]), # First: check rate limits
HookMatcher(hooks=[authorization_check]), # Second: verify permissions
HookMatcher(hooks=[input_sanitizer]), # Third: sanitize inputs
HookMatcher(hooks=[audit_logger]) # Last: log the action
]
}
)Verwende Regex-Muster, um mehrere Tools abzugleichen:
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])
]
}
)Matcher gleichen nur Tool-Namen ab, nicht Dateipfade oder andere Argumente. Um nach Dateipfad zu filtern, überprüfe tool_input.file_path in deinem Hook-Callback.
Verwende SubagentStop-Hooks, um die Subagent-Fertigstellung zu überwachen. Die tool_use_id hilft, Parent-Agent-Aufrufe mit ihren Subagents zu korrelieren:
async def subagent_tracker(input_data, tool_use_id, context):
if input_data['hook_event_name'] == 'SubagentStop':
print(f"[SUBAGENT] Completed")
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])]
}
)Hooks können asynchrone Operationen wie HTTP-Anfragen durchführen. Behandle Fehler elegant, indem du Ausnahmen abfängst, anstatt sie zu werfen. In TypeScript übergebe das signal an fetch(), damit die Anfrage storniert wird, wenn der Hook das Timeout überschreitet:
import aiohttp
from datetime import datetime
async def webhook_notifier(input_data, tool_use_id, context):
if input_data['hook_event_name'] != 'PostToolUse':
return {}
try:
async with aiohttp.ClientSession() as session:
await session.post(
'https://api.example.com/webhook',
json={
'tool': input_data['tool_name'],
'timestamp': datetime.now().isoformat()
}
)
except Exception as e:
print(f'Webhook request failed: {e}')
return {}Verwende Notification-Hooks, um Statusaktualisierungen vom Agent zu erhalten und sie an externe Dienste wie Slack oder Überwachungs-Dashboards weiterzuleiten:
import { query, HookCallback, NotificationHookInput } from "@anthropic-ai/claude-agent-sdk";
const notificationHandler: HookCallback = async (input, toolUseID, { signal }) => {
const notification = input as NotificationHookInput;
await fetch('https://hooks.slack.com/services/YOUR/WEBHOOK/URL', {
method: 'POST',
body: JSON.stringify({
text: `Agent status: ${notification.message}`
}),
signal
});
return {};
};
for await (const message of query({
prompt: "Analyze this codebase",
options: {
hooks: {
Notification: [{ hooks: [notificationHandler] }]
}
}
})) {
console.log(message);
}Dieser Abschnitt behandelt häufige Probleme und deren Lösungen.
PreToolUse, nicht preToolUse)options.hooks istSubagentStop-, Stop-, SessionStart-, SessionEnd- und Notification-Hooks werden Matcher ignoriert. Diese Hooks werden für alle Ereignisse dieses Typs ausgelöst.max_turns-Limit erreicht, da die Sitzung endet, bevor Hooks ausgeführt werden könnenMatcher gleichen nur Tool-Namen ab, nicht Dateipfade oder andere Argumente. Um nach Dateipfad zu filtern, überprüfe tool_input.file_path in deinem Hook:
const myHook: HookCallback = async (input, toolUseID, { signal }) => {
const preInput = input as PreToolUseHookInput;
const filePath = preInput.tool_input?.file_path as string;
if (!filePath?.endsWith('.md')) return {}; // Skip non-markdown files
// Process markdown files...
};timeout-Wert in der HookMatcher-KonfigurationAbortSignal aus dem dritten Callback-Argument, um die Stornierung elegant in TypeScript zu handhabenPreToolUse-Hooks auf permissionDecision: 'deny'-RückgabenpermissionDecisionReason sie zurückgebenStelle sicher, dass updatedInput innerhalb von hookSpecificOutput ist, nicht auf der obersten Ebene:
return {
hookSpecificOutput: {
hookEventName: input.hook_event_name,
permissionDecision: 'allow',
updatedInput: { command: 'new command' }
}
};Du musst auch permissionDecision: 'allow' zurückgeben, damit die Eingabeänderung wirksam wird
Schließe hookEventName in hookSpecificOutput ein, um zu identifizieren, welcher Hook-Typ die Ausgabe ist
SessionStart-, SessionEnd- und Notification-Hooks sind nur im TypeScript SDK verfügbar. Das Python SDK unterstützt diese Ereignisse aufgrund von Setup-Einschränkungen nicht.
Beim Spawnen mehrerer Subagents kann jeder einzelne Genehmigungen separat anfordern. Subagents erben nicht automatisch Parent-Agent-Genehmigungen. Um wiederholte Aufforderungen zu vermeiden, verwende PreToolUse-Hooks, um spezifische Tools automatisch zu genehmigen, oder konfiguriere Genehmigungsregeln, die für Subagent-Sitzungen gelten.
Ein UserPromptSubmit-Hook, der Subagents spawnt, kann unendliche Schleifen erstellen, wenn diese Subagents denselben Hook auslösen. Um dies zu verhindern:
parent_tool_use_id-Feld, um zu erkennen, ob du bereits in einem Subagent-Kontext bistDas systemMessage-Feld fügt Kontext zum Gespräch hinzu, das das Modell sieht, aber es wird möglicherweise nicht in allen SDK-Ausgabemodi angezeigt. Wenn du Hook-Entscheidungen für deine Anwendung anzeigen musst, protokolliere sie separat oder verwende einen dedizierten Ausgabekanal.
Was this page helpful?