SpyBara
Go Premium

agent-sdk/hooks.md 2026-09-17 05:00 UTC to 2026-09-18 23:58 UTC

This page contains 5 additions and 2 deletions.

2026
Wed 9 22:58 Mon 14 22:58 Fri 18 23:58

Agentverhalten mit Hooks abfangen und steuern

Fangen Sie Agentverhalten an wichtigen Ausführungspunkten mit Hooks ab und passen Sie es an

Hooks sind Callback-Funktionen, die Ihren Code als Reaktion auf Agent-Ereignisse ausführen, z. B. wenn ein Tool aufgerufen wird, eine Sitzung startet oder die Ausführung stoppt. Mit Hooks können Sie:

  • Gefährliche Operationen blockieren, bevor sie ausgeführt werden, z. B. destruktive Shell-Befehle oder nicht autorisierter Dateizugriff
  • Alle Tool-Aufrufe protokollieren und überprüfen für Compliance, Debugging oder Analytik
  • Eingaben und Ausgaben transformieren, um Daten zu bereinigen, Anmeldedaten einzufügen oder Dateipfade umzuleiten
  • Menschliche Genehmigung anfordern für sensible Aktionen wie Datenbankschreibvorgänge oder API-Aufrufe
  • Sitzungslebenszyklus verfolgen, um den Status zu verwalten, Ressourcen freizugeben oder Benachrichtigungen zu senden

Funktionsweise von Hooks

1

Ein Ereignis wird ausgelöst

Während der Agent-Ausführung passiert etwas und das SDK löst ein Ereignis aus: Ein Tool wird aufgerufen (PreToolUse), ein Tool gibt ein Ergebnis zurück (PostToolUse), ein Subagent startet oder stoppt, der Agent ist untätig oder die Ausführung ist beendet. Siehe die vollständige Liste der Ereignisse.

2

Das SDK sammelt registrierte Hooks

Das SDK prüft auf Hooks, die für diesen Ereignistyp registriert sind. Dies umfasst Callback-Hooks, die Sie in options.hooks übergeben, und Shell-Befehls-Hooks aus Einstellungsdateien, wenn der entsprechende settingSources oder setting_sources Eintrag aktiviert ist, was für Standard-query()-Optionen der Fall ist.

3

Matcher filtern, welche Hooks ausgeführt werden

Wenn ein Hook ein matcher Muster hat (z. B. "Write|Edit"), testet das SDK es gegen das Ziel des Ereignisses (z. B. den Tool-Namen). Hooks ohne Matcher werden für jedes Ereignis dieses Typs ausgeführt.

4

Callback-Funktionen werden ausgeführt

Jede übereinstimmende Hook-Callback-Funktion erhält Eingaben über das, was passiert: den Tool-Namen, seine Argumente, die Sitzungs-ID und andere ereignisspezifische Details.

5

Ihr Callback gibt eine Entscheidung zurück

Nach dem Ausführen von Operationen (Protokollierung, API-Aufrufe, Validierung) gibt Ihr Callback ein Ausgabeobjekt zurück, das dem Agent mitteilt, was zu tun ist: die Operation zulassen, blockieren, die Eingabe ändern oder Kontext in das Gespräch einfügen.

Das folgende Beispiel bringt diese Schritte zusammen. Es registriert einen PreToolUse Hook (Schritt 1) mit einem "Write|Edit" Matcher (Schritt 3), sodass der Callback nur für Datei-Schreib-Tools ausgelöst wird. Wenn ausgelöst, erhält der Callback die Eingabe des Tools (Schritt 4), prüft, ob der Dateipfad auf eine .env-Datei abzielt, und gibt permissionDecision: "deny" zurück, um die Operation zu blockieren (Schritt 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("Create a .env file with the standard local development 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())

Wenn Sie eines der Skripte ausführen, versucht Claude, die .env-Datei zu erstellen, der Hook verweigert den Tool-Aufruf, und Claudes endgültige Antwort erklärt, dass es keine .env-Dateien erstellen kann.

Verfügbare Hooks

Das SDK bietet Hooks für verschiedene Phasen der Agent-Ausführung. Einige Hooks sind in beiden SDKs verfügbar, während andere nur für TypeScript verfügbar sind.

Hook-Ereignis Python SDK TypeScript SDK Was löst es aus Beispiel-Anwendungsfall
PreToolUse Ja Ja Tool-Aufrufanforderung (kann blockiert oder geändert werden) Gefährliche Shell-Befehle blockieren
PostToolUse Ja Ja Tool-Ausführungsergebnis Alle Dateiänderungen im Audit-Trail protokollieren
PostToolUseFailure Ja Ja Tool-Ausführungsfehler Tool-Fehler behandeln oder protokollieren
PostToolBatch Nein Ja Ein vollständiger Batch von Tool-Aufrufen wird aufgelöst, einmal pro Batch vor dem nächsten Modellaufruf Konventionen einmal für den gesamten Batch einfügen
UserPromptSubmit Ja Ja Benutzer-Prompt-Übermittlung Zusätzlichen Kontext in Prompts einfügen
UserPromptExpansion Nein Ja Ein von Benutzern eingegebener Befehl oder ein MCP-Prompt wird zu einem Prompt erweitert, bevor er Claude erreicht. Wird nicht ausgelöst, wenn Claude selbst eine Skill aufruft Einen Befehl von direkter Aufrufe blockieren oder Kontext hinzufügen, wenn eine Skill eingegeben wird
MessageDisplay Nein Ja Eine Assistenten-Nachricht mit Text wird abgeschlossen, einmal pro Nachricht mit dem vollständigen Nachrichtentext Angezeigten Text redigieren oder neu formatieren, ohne das Transkript zu ändern
Stop Ja Ja Agent-Ausführung stoppt Sitzungsstatus vor dem Beenden speichern
StopFailure Nein Ja Die Runde endet mit einem API-Fehler statt mit einem normalen Stopp Fehler protokollieren oder Benachrichtigungen senden
SubagentStart Ja Ja Subagent-Initialisierung Parallele Task-Spawning verfolgen
SubagentStop Ja Ja Subagent-Fertigstellung Ergebnisse aus parallelen Tasks aggregieren
PreCompact Ja Ja Anforderung zur Gesprächskomprimierung Vollständiges Transkript vor der Zusammenfassung archivieren
PostCompact Nein Ja Gesprächskomprimierung ist abgeschlossen Die generierte Zusammenfassung protokollieren
PreModelSwitch Nein Ja Ein angefordeter Modellwechsel, bevor er stattfindet (kann blockiert werden) Wechsel zu einem bestimmten Modell blockieren
PostModelSwitch Nein Ja Das Modell der Sitzung ändert sich, einschließlich eines automatischen Fallbacks Claude modellspezifische Anleitung für das neue Modell geben
PermissionRequest Ja Ja Ein Tool-Aufruf benötigt eine Berechtigungsentscheidung Benutzerdefinierte Berechtigungsbehandlung
PermissionDenied Nein Ja Auto-Modus verweigert einen Tool-Aufruf, einschließlich Verweigerungen ohne Klassifizierer-Urteil Verweigerungen protokollieren oder dem Modell mitteilen, dass es möglicherweise erneut versuchen kann; Claude Code ignoriert retry: true für Verweigerungen ohne Urteil. Siehe PermissionDenied
SessionStart Nein Ja Sitzungsinitialisierung Protokollierung und Telemetrie initialisieren
SessionEnd Nein Ja Sitzungsbeendigung Temporäre Ressourcen bereinigen
Notification Ja Ja Agent-Statusmeldungen Agent-Status-Updates an Slack oder PagerDuty senden
Setup Nein Ja Sitzungssetup/Wartung Initialisierungsaufgaben ausführen
TeammateIdle Nein Ja Teammate wird untätig Arbeit neu zuweisen oder benachrichtigen
TaskCreated Nein Ja Eine Task wird über das TaskCreate-Tool erstellt Task-Benennungskonventionen durchsetzen
TaskCompleted Nein Ja Eine Task wird als abgeschlossen markiert Bestandene Tests vor dem Schließen einer Task erforderlich
Elicitation Nein Ja Ein MCP-Server fordert Benutzereingaben während einer Task an Auf MCP-Eingabeanforderungen programmgesteuert reagieren
ElicitationResult Nein Ja Ein Benutzer antwortet auf eine MCP-Elicitation Die Antwort ändern oder blockieren, bevor sie an den Server zurückgeht
ConfigChange Nein Ja Konfigurationsdatei ändert sich Einstellungen dynamisch neu laden
InstructionsLoaded Nein Ja Eine CLAUDE.md- oder Regeldatei wird in den Kontext geladen Überprüfen, welche Anweisungsdateien geladen werden
WorktreeCreate Nein Ja Git Worktree erstellt Isolierte Workspaces verfolgen
WorktreeRemove Nein Ja Git Worktree entfernt Workspace-Ressourcen bereinigen
CwdChanged Nein Ja Das Arbeitsverzeichnis ändert sich während einer Sitzung Umgebungsvariablen pro Verzeichnis neu laden
FileChanged Nein Ja Eine überwachte Datei wird geändert, erstellt oder gelöscht Konfiguration neu laden, wenn sich Projektdateien ändern
DirectoryAdded Nein Ja Ein Arbeitsverzeichnis wird während einer Sitzung hinzugefügt Abhängigkeiten für ein während der Sitzung hinzugefügtes Repository installieren

Hooks konfigurieren

Um einen Hook zu konfigurieren, übergeben Sie ihn im hooks Feld Ihrer Agent-Optionen (ClaudeAgentOptions in Python, das options Objekt in TypeScript). Dieser Ausschnitt setzt voraus, dass Sie bereits einen Hook-Callback definiert haben, wie protect_env_files in Python oder protectEnvFiles in TypeScript aus dem obigen Beispiel:

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)

Die hooks Option ist ein Wörterbuch in Python oder ein Objekt in TypeScript, wobei:

Matcher

Verwenden Sie Matcher, um zu filtern, wann Ihre Callbacks ausgelöst werden. Das matcher Feld wird gegen einen anderen Wert abgeglichen, je nach Hook-Ereignistyp. Beispielsweise werden Tool-basierte Hooks gegen den Tool-Namen abgeglichen, während Notification Hooks gegen den Benachrichtigungstyp abgeglichen werden.

SDK-Matcher folgen den gleichen Regeln wie Matcher in Einstellungsdateien. Dieser Abschnitt dokumentiert die Pfade für exakte Zeichenketten- und reguläre Ausdrucksbewertung, ihre Versionsanforderungen und die Matcher-Werte für jeden Ereignistyp.

Option Typ Standard Beschreibung
matcher string undefined Muster, das gegen das Filterfeld des Ereignisses abgeglichen wird, nach den Regeln für Matcher in Einstellungsdateien. Für Tool-Hooks ist dies der Tool-Name. Integrierte Tools umfassen Bash, Read, Write, Edit, Glob, Grep, WebFetch, Agent und andere (siehe Tool-Eingabetypen für die vollständige Liste). MCP-Tools verwenden das Muster mcp__<server>__<action>, wobei <server> der Schlüssel ist, den Sie in der mcpServers Konfiguration verwenden.
hooks HookCallback[] - Erforderlich. Array von Callback-Funktionen, die ausgeführt werden, wenn das Muster übereinstimmt
timeout number undefined Timeout in Sekunden. Wenn weggelassen, wendet Claude Code das Standard-Timeout des Ereignisses an. Ihre SDK-Callbacks folgen den command Hook-Standards

Verwenden Sie 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 Ihre Callbacks für jedes Vorkommen des Ereignisses ausführt. Lassen Sie es absichtlich weg, um jeden Tool-Aufruf Ihrer Sitzung zu protokollieren.

Callback-Funktionen

Eingaben

Jeder Hook-Callback erhält drei Argumente:

  • Eingabedaten: ein typisiertes Objekt mit Ereignisdetails. Jeder Hook-Typ hat seine eigene Eingabeform. Beispielsweise enthält PreToolUseHookInput tool_name und tool_input, während NotificationHookInput message enthält. Siehe die vollständigen Typdefinitionen in den TypeScript und Python SDK-Referenzen.
    • Alle Hook-Eingaben teilen session_id, cwd und hook_event_name.
    • agent_id und agent_type werden ausgefüllt, wenn der Hook in einem Subagent ausgelöst wird. In TypeScript befinden sich diese in der Basis-Hook-Eingabe und sind für alle Hook-Typen verfügbar. In Python sind sie optionale Felder auf PreToolUse, PostToolUse, PostToolUseFailure und PermissionRequest, und erforderliche Felder auf SubagentStart und SubagentStop.
  • Tool-Verwendungs-ID (str | None / string | undefined): korreliert PreToolUse und PostToolUse Ereignisse für denselben Tool-Aufruf.
  • Kontext: In TypeScript enthält eine signal Eigenschaft (AbortSignal) für Abbruch. In Python ist dieses Argument für zukünftige Verwendung reserviert.

Ausgaben

Ihr Callback gibt ein Objekt mit zwei Kategorien von Feldern zurück:

  • Top-Level-Felder werden bei jedem Ereignis akzeptiert: systemMessage zeigt eine Nachricht für den Benutzer an, und continue (continue_ in Python) bestimmt, ob der Agent nach diesem Hook weiterläuft. Einige Ereignisse verwerfen sie oder liefern sie an anderer Stelle. Jeder Abschnitt des Ereignisses auf der Hooks-Seite sagt, wo sie landen.
  • hookSpecificOutput steuert die aktuelle Operation. Die Felder, die Sie darin setzen, hängen vom Hook-Ereignistyp ab:
    • Für PreToolUse Hooks ist dies der Ort, an dem Sie permissionDecision ("allow", "deny", "ask" oder "defer"), permissionDecisionReason und updatedInput setzen. Wenn Sie "defer" zurückgeben, endet die Abfrage, damit Sie sie später fortsetzen können.
    • Für PostToolUse Hooks können Sie additionalContext setzen, um Informationen zum Tool-Ergebnis anzuhängen. Um die Ausgabe des Tools vor Claude zu ersetzen, setzen Sie updatedToolOutput, das für jedes Tool in beiden SDKs funktioniert. Das ältere updatedMCPToolOutput Feld ersetzt nur MCP-Tool-Ausgabe und ist veraltet.
    • Im TypeScript SDK kann ein PostToolUse Callback auch classifierContext zurückgeben, eine kurze Notiz über das Ergebnis des Tool-Aufrufs für den Auto-Modus Berechtigungsklassifizierer. Da Ihr Callback in Ihrem eigenen Anwendungsprozess ausgeführt wird, kann der Klassifizierer eine Benutzeraussage, die Sie in der Notiz weitergeben, als Benutzerabsicht gewichten. Das Feld erfordert TypeScript Agent SDK v0.3.236 oder später. Annotieren Sie ein Ergebnis für den Auto-Modus Klassifizierer behandelt die Längenbegrenzung, die Nur-Synchron-Regel und was nicht in die Notiz gehört.

Geben Sie {} zurück, um die Operation ohne Änderungen zuzulassen. SDK-Callback-Hooks verwenden das gleiche JSON-Ausgabeformat wie Claude Code Shell-Befehls-Hooks, das jedes Feld und ereignisspezifische Option dokumentiert. Für die SDK-Typdefinitionen siehe die TypeScript und Python SDK-Referenzen.

Asynchrone Ausgabe

Standardmäßig wartet der Agent darauf, dass Ihr Hook zurückkommt, bevor er fortfährt. Wenn Ihr Hook einen Nebeneffekt ausführt, wie Protokollierung oder Webhook-Versand, und das Verhalten des Agenten nicht beeinflussen muss, können Sie stattdessen eine asynchrone Ausgabe zurückgeben. Dies teilt dem Agent mit, dass er sofort fortfahren soll, ohne auf die Fertigstellung des Hooks zu warten. In diesem Ausschnitt stehen send_to_logging_service in Python und sendToLoggingService in TypeScript für jede Protokollierungsfunktion, die Sie definieren:

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}
Feld Typ Beschreibung
async true Signalisiert Async-Modus. Der Agent fährt fort, ohne zu warten. In Python verwenden Sie async_, um das reservierte Schlüsselwort zu vermeiden.
asyncTimeout number Optionales Timeout in Millisekunden für die Hintergrund-Operation

Beispiele

Mehrere Beispiele in diesem Abschnitt zeigen nur die Callback-Funktion. Um eines auszuführen, registrieren Sie den Callback unter dem entsprechenden Ereignis im hooks Feld Ihrer Optionen, wie in Hooks konfigurieren gezeigt.

Tool-Eingabe ändern

Dieses Beispiel fängt Write-Tool-Aufrufe ab und schreibt das file_path Argument um, um /sandbox voranzustellen, wodurch alle Datei-Schreibvorgänge in ein Sandbox-Verzeichnis umgeleitet werden. Der Callback gibt updatedInput mit dem geänderten Pfad und permissionDecision: 'allow' zurück, um die umgeschriebene Operation automatisch zu genehmigen:

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

Um die Umleitung zu bestätigen, setzen Sie das Präfix auf einen Pfad, in den Sie schreiben können, z. B. ./sandbox oder /tmp/sandbox (macOS erlaubt nicht das Erstellen eines Root-Level /sandbox Verzeichnisses), und bitten Sie dann den Agent, eine Datei zu schreiben: Das Ergebnis des Write-Tools im Nachrichtenstrom nennt den Pfad mit Ihrem Sandbox-Präfix anstelle des von Claude angeforderten.

Kontext hinzufügen und ein Tool blockieren

Dieses Beispiel blockiert Schreibvorgänge in das /etc Verzeichnis und erklärt den Grund sowohl dem Modell als auch dem Benutzer:

  • permissionDecision: 'deny' stoppt den Tool-Aufruf.
  • permissionDecisionReason teilt dem Modell mit, warum, damit es nicht erneut versucht.
  • systemMessage zeigt dem Benutzer, was passiert ist.
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: message shown to the user
"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 {}

Spezifische Tools automatisch genehmigen

Standardmäßig kann der Agent vor der Verwendung bestimmter Tools um Genehmigung bitten. Dieses Beispiel genehmigt schreibgeschützte Dateisystem-Tools (Read, Glob, Grep) automatisch, indem permissionDecision: 'allow' zurückgegeben wird, sodass sie ohne Benutzerbestätigung ausgeführt werden, während alle anderen Tools normalen Berechtigungsprüfungen unterliegen:

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

Mehrere Hooks registrieren

Wenn ein Ereignis ausgelöst wird, werden alle übereinstimmenden Hooks parallel ausgeführt. Bei Berechtigungsentscheidungen gewinnt das restriktivste Ergebnis: Ein einzelnes deny blockiert den Tool-Aufruf, unabhängig davon, was die anderen Hooks zurückgeben. Da die Abschlussreihenfolge nicht deterministisch ist, schreiben Sie jeden Hook so, dass er unabhängig agiert, anstatt sich darauf zu verlassen, dass ein anderer Hook zuerst ausgeführt wurde.

Das folgende Beispiel registriert drei unabhängige Prüfungen für jeden Tool-Aufruf:

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

Mit Multi-Tool-Matchern filtern

Verwenden Sie Multi-Tool-Matcher, um einen Callback über verwandte Tools hinweg zu teilen. Dieses Beispiel registriert drei Matcher mit unterschiedlichen Bereichen:

  • Eine durch Pipe getrennte exakte Liste (Write|Edit|NotebookEdit) löst file_security_hook nur für Datei-Änderungs-Tools aus.
  • Ein Regex (^mcp__) löst mcp_audit_hook für alle MCP-Tools aus, deren Namen mit mcp__ beginnen.
  • Ein weggelassener Matcher löst global_logger für jeden Tool-Aufruf unabhängig vom Namen aus.
options = ClaudeAgentOptions(
hooks={
"PreToolUse": [
# Match file modification tools
HookMatcher(matcher="Write|Edit|NotebookEdit", hooks=[file_security_hook]),
# Match all MCP tools
HookMatcher(matcher="^mcp__", hooks=[mcp_audit_hook]),
# Match everything (no matcher)
HookMatcher(hooks=[global_logger]),
]
}
)

Subagent-Aktivität verfolgen

Verwenden Sie SubagentStop Hooks, um zu überwachen, wenn Subagents ihre Arbeit beenden. Siehe den vollständigen Eingabetyp in den TypeScript und Python SDK-Referenzen. Dieses Beispiel protokolliert eine Zusammenfassung jedes Mal, wenn ein Subagent abgeschlossen wird:

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

HTTP-Anfragen von Hooks aus stellen

Hooks können asynchrone Operationen wie HTTP-Anfragen ausführen. Fangen Sie Fehler in Ihrem Hook ab, anstatt sie zu propagieren.

Dieses Beispiel sendet einen Webhook nach jeder Tool-Fertigstellung und protokolliert, welches Tool ausgeführt wurde und wann. Der Hook fängt Fehler von einem fehlgeschlagenen Webhook ab:

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
print(f"Webhook request failed: {e}")

return {}

Um zu bestätigen, dass der Hook ausgelöst wird, verweisen Sie die Webhook-URL auf einen Endpunkt, den Sie überwachen können, und senden Sie eine Eingabeaufforderung, die ein Tool verwendet: Der Hook sendet einen POST mit dem Tool-Namen und dem Zeitstempel nach jeder Tool-Fertigstellung.

Benachrichtigungen an Slack weiterleiten

Verwenden Sie Notification Hooks, um Systembenachrichtigungen vom Agent zu empfangen und sie an externe Dienste weiterzuleiten. In SDK-Sitzungen führt Claude Code diesen Hook für die folgenden Benachrichtigungstypen aus:

  • permission_prompt sobald eine Berechtigungsanfrage etwa sechs Sekunden auf Ihrem canUseTool Callback gewartet hat. Erfordert TypeScript Agent SDK v0.3.233 oder später oder Python Agent SDK v0.2.139 oder später
  • elicitation_complete und elicitation_response für Benutzer-Abfrage-Flows

Claude Code gibt die anderen Typen aus, wie idle_prompt, auth_success und elicitation_dialog, aus interaktiver Benutzeroberfläche, die SDK-Sitzungen nicht ausführen.

Jede Benachrichtigung enthält ein message Feld mit einer für Menschen lesbaren Beschreibung und optional einen title.

Dieses Beispiel leitet jede Benachrichtigung an einen Slack-Kanal weiter. Es erfordert eine Slack Incoming Webhook URL, die Sie erstellen, indem Sie eine App zu Ihrem Slack-Workspace hinzufügen und Incoming Webhooks aktivieren:

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())

Wenn ein Notification Ereignis ausgelöst wird, sendet der Hook die message der Benachrichtigung, mit dem Präfix Agent status:, an den Kanal, auf den Ihr Webhook abzielt.

Häufige Probleme beheben

Hook wird nicht ausgelöst

  • Überprüfen Sie, ob der Hook-Ereignisname korrekt und case-sensitiv ist (PreToolUse, nicht preToolUse)
  • Überprüfen Sie, ob Ihr Matcher-Muster den Tool-Namen genau abgleicht
  • Stellen Sie sicher, dass der Hook unter dem richtigen Ereignistyp in options.hooks ist
  • Für Nicht-Tool-Hooks, die Matcher unterstützen, wie Notification und SubagentStop, gleichen Matcher gegen verschiedene Felder ab, und Stop ignoriert Matcher vollständig (siehe Matcher-Muster)
  • Hooks werden möglicherweise nicht ausgelöst, wenn der Agent das max_turns Limit erreicht, da die Sitzung endet, bevor Hooks ausgeführt werden können

Matcher filtert nicht wie erwartet

Matcher gleichen nur Tool-Namen ab, nicht Dateipfade oder andere Argumente. Um nach Dateipfad zu filtern, prüfen Sie tool_input.file_path in Ihrem 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 {};
};

Hook-Timeout

Claude Code führt jeden Callback mit einem Timeout aus, den Sie in Sekunden mit dem Feld timeout auf seinem HookMatcher festlegen. Wenn Sie keinen festlegen, verwendet Claude Code den Standard des Ereignisses: 600 Sekunden für die meisten Ereignisse, 30 Sekunden für UserPromptSubmit, PreModelSwitch und PostModelSwitch, und 10 Sekunden für MessageDisplay. Claude Code führt SessionEnd Callbacks während des Herunterfahrens unter dem kürzeren SessionEnd-Timeout-Budget aus, standardmäßig 1,5 Sekunden.

Wenn ein Callback sein Timeout überschreitet, bricht Claude Code es ab und verwirft seine Ausgabe, und die Sitzung wird fortgesetzt, anstatt zu hängen. Was danach passiert, hängt vom Ereignis ab:

  • PreToolUse: Claude Code führt den Tool-Aufruf nicht aus, Claude erhält ein Tool-Ergebnis, das besagt, dass der Hook nicht vor seinem Timeout geantwortet hat, und der Turn wird fortgesetzt. Wenn ein anderer PreToolUse Hook eine explizite Ablehnung zurückgegeben hat, erhält Claude stattdessen diese Ablehnung. Vor v2.1.210 meldete Claude Code das Timeout an Claude als Benutzerabweisung, was unbeaufsichtigte Sitzungen zum Stoppen und Warten auf Eingabe führte.
  • PostToolUse und PostToolUseFailure: Claude Code behält das Tool-Ergebnis bei und der Turn wird fortgesetzt.
  • UserPromptSubmit und UserPromptExpansion: Claude Code blockiert die Aufforderung mit einer Nachricht, die den Hook und das Timeout benennt, und die Sitzung wird fortgesetzt. Da ein Callback bei diesen Ereignissen als Richtlinien-Gate fungieren kann, lässt Claude Code niemals eine abgelaufene Aufforderung ungeprüft durch. Vor v2.1.208 endete Claude Code die Abfrage mit error_during_execution, wenn ein Callback bei diesen Ereignissen abgelaufen ist.
  • Stop und SubagentStop: der abgelaufene Callback zählt als Rückgabe ohne Entscheidung. Der Agent oder Subagent stoppt, als hätte dieser Callback es erlaubt, und eine Entscheidung von Ihren anderen Hooks bei dem Ereignis wird immer noch angewendet. Vor Claude Code v2.1.273 zählte ein abgelaufener Stop oder SubagentStop Callback als fehlgeschlagener Hook-Lauf, und Claude Code verwarf die Entscheidungen Ihrer anderen Hooks bei dem Ereignis.
  • SessionStart: der abgelaufene Callback zählt als Rückgabe ohne Ausgabe, und die Sitzung wird mit der Ausgabe Ihrer anderen SessionStart Hooks fortgesetzt.
  • PreModelSwitch: Claude Code blockiert den Modellwechsel. Ein Hook, der nicht antwortet, hat den Wechsel nicht genehmigt.
  • Andere Ereignisse, wie Notification, PreCompact und PostModelSwitch: Claude Code protokolliert den Fehler und wird fortgesetzt.

Das erste Mal, wenn ein Stop oder SessionStart Callback in der Hauptsitzung abläuft, fügt Claude Code auch eine SDKInformationalMessage zum Nachrichtenstrom hinzu, die besagt, dass die App, die die Sitzung steuert, nicht geantwortet hat. Spätere Timeouts wiederholen diese Nachricht nicht, während Ihre App nicht reagiert.

Wenn Sie die Abfrage unterbrechen, während ein Callback ausstehend ist, bricht Claude Code den ausstehenden Tool-Aufruf ab. Vor v2.1.208 konnte der Tool-Aufruf noch fortfahren, wenn Sie während eines ausstehenden PreToolUse Callbacks unterbrochen haben.

Wenn Ihr Callback mehr Zeit benötigt, legen Sie einen höheren timeout auf seinem HookMatcher fest. In TypeScript verwenden Sie das AbortSignal aus dem dritten Callback-Argument, um Abbruch elegant zu behandeln, wenn das Timeout abläuft.

Tool wird unerwartet blockiert

  • Überprüfen Sie alle PreToolUse Hooks auf permissionDecision: 'deny' Rückgaben
  • Fügen Sie Protokollierung zu Ihren Hooks hinzu, um zu sehen, welche permissionDecisionReason sie zurückgeben
  • Überprüfen Sie, ob Matcher-Muster nicht zu breit sind: ein leerer Matcher gleicht alle Tools ab

Geänderte Eingabe wird nicht angewendet

  • Stellen Sie sicher, dass updatedInput in hookSpecificOutput ist, nicht auf der obersten Ebene:

    return {
      hookSpecificOutput: {
        hookEventName: "PreToolUse",
        permissionDecision: "allow",
        updatedInput: { command: "new command" }
      }
    };
    
  • Kombinieren Sie updatedInput nicht mit permissionDecision: 'defer', das die geänderte Eingabe verwirft. Das Weglassen von permissionDecision ist in Ordnung: die geänderte Eingabe wird immer noch durch die normale Berechtigungsevaluierung angewendet. Sie können auch 'allow' zurückgeben, um die geänderte Eingabe automatisch zu genehmigen, oder 'ask', um sie dem Benutzer zur Genehmigung anzuzeigen

  • Schließen Sie hookEventName in hookSpecificOutput ein, um zu identifizieren, für welchen Hook-Typ die Ausgabe bestimmt ist

Sitzungs-Hooks nicht in Python verfügbar

SessionStart und SessionEnd können als SDK-Callback-Hooks in TypeScript registriert werden, sind aber im Python SDK nicht verfügbar, da sein HookEvent Typ sie auslässt. In Python sind sie nur als Shell-Befehls-Hooks verfügbar, die in Einstellungsdateien wie .claude/settings.json definiert sind. Um Shell-Befehls-Hooks aus Ihrer SDK-Anwendung zu laden, schließen Sie die entsprechende Einstellungsquelle mit setting_sources oder settingSources ein:

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

Um stattdessen Initialisierungslogik als Python SDK-Callback auszuführen, verwenden Sie die erste Nachricht von client.receive_response() als Auslöser.

Subagent-Berechtigungsaufforderungen vervielfachen sich

Beim Spawnen mehrerer Subagents kann jeder einzelne Berechtigungen separat anfordern. Um wiederholte Aufforderungen zu vermeiden, verwenden Sie PreToolUse Hooks, um spezifische Tools automatisch zu genehmigen, oder konfigurieren Sie Berechtigungsregeln, die Subagents vom übergeordneten Gespräch erben.

Rekursive Hook-Schleifen mit Subagents

Ein UserPromptSubmit Hook, der Subagents spawnt, kann unendliche Schleifen erzeugen, wenn diese Subagents denselben Hook auslösen. Um dies zu verhindern:

  • Verwenden Sie eine gemeinsame Variable oder Sitzungsstatus, um zu verfolgen, ob Sie bereits in einem Subagent sind
  • Beschränken Sie Hooks so, dass sie nur für die Top-Level-Agent-Sitzung ausgeführt werden

systemMessage wird nicht in der Ausgabe angezeigt

Das systemMessage Feld zeigt eine Nachricht für den Benutzer an, nicht für das Modell. Auf Claude Code v2.1.227 oder später kann die systemMessage eines Hooks in dem Nachrichtenstrom als SDKInformationalMessage auftauchen. Ob dies der Fall ist, hängt vom Ereignis ab. Der Abschnitt jedes Ereignisses auf der Hooks-Seite sagt, wie die Ausgabe auftaucht. Um stattdessen Kontext an das Modell zu übergeben, geben Sie additionalContext zurück.

Vor v2.1.227 gab das SDK Hook-Ausgaben im Nachrichtenstrom nur für SessionStart und Setup Hooks aus. Für jedes andere Ereignis erschien die Ausgabe nur in den Lebenszyklusereignissen, die includeHookEvents (include_hook_events in Python) hinzufügt. Der Eintrag dieser Option behandelt, welche Lebenszyklusereignisse jedes Hook-Ereignis erzeugt.

Wenn Sie Hook-Entscheidungen für Ihre Anwendung zuverlässig sichtbar machen müssen, protokollieren Sie sie separat oder verwenden Sie einen dedizierten Ausgabekanal.