Hooks sind Skripte, die automatisch ausgeführt werden, wenn während einer Claude-Code-Session bestimmte Ereignisse eintreten. Sie erhalten JSON-Eingaben über stdin und geben Ergebnisse über Exit-Codes und JSON-Ausgaben zurück. Command-Hooks sind deterministisch, kombinierbar, testbar und sprachunabhängig. Prompt-Hooks und Agent-Hooks nutzen ein Claude-Modell zur Auswertung, sodass ihr Verhalten nicht deterministisch ist. Dieses Modul behandelt das Hook-System, die wichtigsten Ereignisse und wie du nützliche Hooks schreibst.
Hook-Architektur und Konfiguration
Hooks werden in Einstellungsdateien unter einem hooks Key konfiguriert. Jedes Ereignis hat ein Array von Matchern, und jeder Matcher hat ein Array von Hook-Definitionen. Das matcher Feld ist ein Regex-Muster, das gegen den Tool-Namen abgeglichen wird — "Bash" trifft genau zu, "Write|Edit" trifft auf eines von beiden zu, "*" trifft auf alle Tools zu, "mcp__github__.*" trifft auf alle GitHub-MCP-Tools zu.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-bash.py\"",
"timeout": 10
}
]
}
]
}
}
Matcher unterstützen auch ein bedingtes if Feld (v2.1.85), das Permission-Rule-Syntax nutzt, um weiter zu filtern, wann ein Hook auslöst. Während matcher das Tool nach Namen auswählt, if grenzt auf bestimmte Aufrufe dieses Tools ein. Das ist nützlich, wenn dich nur ein Teil der Aufrufe eines Tools interessiert — zum Beispiel das Abfangen von git push Befehlen, ohne bei jedem Bash-Aufruf zu laufen:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"if": "Bash(git push*)",
"hooks": [
{
"type": "command",
"command": "/path/to/check-push.sh"
}
]
}
]
}
}
Das if -Muster folgt derselben Syntax wie Permission-Rules — Bash(git *) trifft auf jeden git-Befehl zu, Write(src/**/test_*.py) trifft auf das Schreiben von Testdateien zu und so weiter. Ein Hook mit einem if -Feld löst nur aus, wenn sowohl der matcher als auch die if -Bedingung zutreffen.
Claude Code unterstützt 30 Hook-Events. Die für die tägliche Arbeit nützlichsten sind PreToolUse (vor der Ausführung eines Tools validieren, kann blockieren), PostToolUse (danach beobachten oder reagieren, kann Kontext hinzufügen), UserPromptSubmit (Nutzereingaben abfangen, bevor Claude sie verarbeitet), und Stop (Prüfungen ausführen, wenn Claude mit dem Antworten fertig ist). Es gibt auch Events für die Berechtigungsbehandlung (PermissionRequest), Benachrichtigungen, den Subagent-Lebenszyklus (SubagentStart, SubagentStop), Fehler (PostToolUseFailure, StopFailure), Konfigurationsänderungen, Dateiüberwachung (FileChanged), Kontext-Kompaktierung (PreCompact, PostCompact) und Worktree-Verwaltung.
Mehrere neuere Events erweitern, worauf Hooks reagieren können. CwdChanged (v2.1.83) löst aus, wenn sich das Arbeitsverzeichnis ändert, und ermöglicht ein reaktives Umgebungsmanagement wie bei direnv — zum Beispiel das automatische Laden von Umgebungsvariablen, wenn Claude ein Projektverzeichnis betritt. TaskCreated (v2.1.84) löst aus, wenn das TaskCreate -Tool verwendet wird, sodass du neue Tasks protokollieren oder validieren kannst, sobald sie gestartet werden. WorktreeCreate (v2.1.84) löst aus, wenn ein Worktree-Agent erstellt wird, und unterstützt type: "http" für Remote-Benachrichtigungen — nützlich, um externe Dienste zu benachrichtigen, wenn parallele Arbeit beginnt. Elicitation (v2.1.76) löst aus, wenn ein MCP-Server mitten in einer Aufgabe über einen interaktiven Dialog strukturierte Nutzereingaben anfordert, und kann die Elicitation abfangen und ändern, bevor sie dem Nutzer angezeigt wird. ElicitationResult (v2.1.76) löst aus, nachdem der Nutzer auf eine MCP-Elicitation geantwortet hat, und kann die Antwort abfangen und überschreiben, bevor sie an den MCP-Server zurückgesendet wird.
PreCompact läuft unmittelbar bevor Claude Code die Konversation komprimiert, um Kontext freizugeben, und kann die Kompaktierung verhindern — nützlich, wenn du den Zustand als Snapshot festhalten, den Nutzer warnen oder eine automatische Kompaktierung verhindern möchtest, die kritischen Kontext verwerfen würde. Das matcher -Feld unterscheidet, was sie ausgelöst hat: "manual" wenn der Nutzer diesen Befehl aufgerufen hat: /compact, und "auto" steht dafür, dass Claude Code automatisch kompaktiert hat, weil der Kontext voll wurde. Blockiere mit Exit-Code 2 oder mit einer JSON-Entscheidungs-Payload:
{
"hooks": {
"PreCompact": [
{
"matcher": "auto",
"hooks": [
{ "type": "command", "command": "./scripts/snapshot-context.sh" }
]
}
]
}
}
Gib {"decision": "block", "reason": "active refactor in flight"} aus dem Skript zurück, um die Konversation unverändert zu lassen. PostCompact löst aus, nachdem die Kompaktierung erfolgreich war — der richtige Ort, um Notizen erneut anzuhängen, einen Skill erneut aufzurufen oder zu protokollieren, was erhalten geblieben ist.
Hook-Skripte erhalten JSON über stdin und haben Zugriff auf mehrere Umgebungsvariablen, die Claude Code automatisch setzt. CLAUDE_CODE_SESSION_ID enthält den eindeutigen Session-Identifier — nutze ihn, um Hook-Logs und externe Telemetrie einer bestimmten Session zuzuordnen.
Ein Python-Hook liest die JSON-Eingabe so:
import json, sys, os
data = json.load(sys.stdin)
tool_name = data.get("tool_name", "")
tool_input = data.get("tool_input", {})
session_id = os.environ.get("CLAUDE_CODE_SESSION_ID", "")
Exit-Code 0 bedeutet Erfolg (JSON-stdout für die Ausgabe parsen). Exit-Code 2 bedeutet einen blockierenden Fehler — Claude stoppt und zeigt deine stderr-Nachricht an. Jeder andere Exit-Code ist eine nicht blockierende Warnung, die im Verbose-Modus angezeigt wird.
Die Hook-Eingabe enthält ein effort -Objekt mit der aktiven Effort-Stufe: { "effort": { "level": "medium" } }. Verfügbare Stufen sind low, medium, high, xhigh, max, und auto. Derselbe Wert ist auch als die $CLAUDE_EFFORT Umgebungsvariable in Hook-Skripten verfügbar, und auch von Hooks ausgeführte Bash-Tool-Befehle können ihn lesen:
import json, os, sys
data = json.load(sys.stdin)
effort_level = data.get("effort", {}).get("level", "medium") # from JSON
effort_env = os.environ.get("CLAUDE_EFFORT", "medium") # from env var
Gängige Hook-Typen und -Muster
Command-Hooks unterstützen zwei Formen. Shell-Form (Standard) übergibt die command String an eine Shell zur Tokenisierung. Exec-Form setzt ein args Array neben dem command, wodurch der Prozess direkt ohne Shell gestartet wird — das vermeidet Probleme mit Shell-Escaping und ist sicherer für Befehle mit benutzerdefinierten Argumenten:
{
"type": "command",
"command": "node",
"args": ["./scripts/validate.js", "--strict"]
}
Hooks können auf fünf Arten laufen. command Hooks führen lokale Shell-Befehle aus. prompt Hooks bitten Claude, einen Prompt auszuwerten, meist bei Stop oder SubagentStop. agent Hooks starten einen Subagent für mehrstufige Validierung. http Hooks senden denselben JSON-Payload per POST an einen Webhook-Endpunkt, was für Remote-Logging oder Policy-Dienste nützlich ist. HTTP-Hooks unterstützen die Interpolation von Umgebungsvariablen in Headern, und diese Variablen müssen explizit auf eine Allowlist gesetzt werden. mcp_tool Hooks rufen ein MCP-Tool direkt auf — nützlich, wenn ein Hook einen externen Dienst aufrufen muss (etwa das Posten in Slack oder das Erstellen eines GitHub-Issues), ohne eine Shell zu bemühen. Hinweis: Der in-App Config Builder erzeugt noch keine mcp_tool Hooks — nutze das JSON-Beispiel unten als Referenz:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "mcp_tool",
"server": "slack",
"tool": "send_message",
"input": { "channel": "#deploys", "text": "Claude finished the task" }
}
]
}
]
}
}
PostToolUse und PostToolUseFailure Hook-Eingaben enthalten ein duration_ms Feld mit der Ausführungszeit des Tools in Millisekunden (ohne Berechtigungsabfragen und PreToolUse-Hooks). Nutze es, um langsame Tools zu verfolgen oder Alerts einzurichten, wenn ein Tool einen Schwellwert überschreitet.
Stop und SubagentStop Hook-Eingaben enthalten jetzt außerdem background_tasks und session_crons Arrays. background_tasks listet die Bash-Befehle und Subagents auf, die beim Ende des Turns noch im Hintergrund liefen; session_crons listet geplante Tasks auf, die an die Session angehängt sind (/schedule, Plugin-Monitore, alles, was über die Cron-Tools eingereiht wurde). Ein Completion-Gate-Hook kann diese nutzen, um Claude weiterarbeiten zu lassen, bis wirklich alles abgeschlossen ist:
import json, sys
data = json.load(sys.stdin)
pending_bg = [t for t in data.get("background_tasks", []) if t.get("status") in ("running", "starting")]
pending_cron = data.get("session_crons", [])
if pending_bg or pending_cron:
print(json.dumps({
"decision": "block",
"reason": f"{len(pending_bg)} background task(s) and {len(pending_cron)} scheduled task(s) still active"
}))
sys.exit(0)
background_tasks ist außerdem praktisch in SubagentStop damit ein übergeordneter Agent sich nicht als fertig erklärt, während seine eigenen gestarteten Bash-Watcher noch laufen.
Häufige Hook-Muster
Auto-Formatierung beim Speichern von Dateien ist einer der nützlichsten Hooks. Ein PostToolUse Hook bei Write|Edit führt deinen Formatter automatisch aus, damit Claudes Ausgabe immer sauber ist:
#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('file_path',''))")
case "$FILE" in
*.ts|*.tsx|*.js) prettier --write "$FILE" 2>/dev/null ;;
*.py) black "$FILE" 2>/dev/null ;;
*.go) gofmt -w "$FILE" 2>/dev/null ;;
esac
exit 0
Sicherheits-Scans bei Schreibvorgängen nutzen PostToolUse mit additionalContext Ausgabe, um Claude vor potenziellen Secrets zu warnen, die es gerade geschrieben hat:
SECRET_PATTERNS = [
(r"api[_-]?key\s*=\s*['\"][^'\"]+['\"]", "Potential hardcoded API key"),
(r"password\s*=\s*['\"][^'\"]+['\"]", "Potential hardcoded password"),
]
# ... check content, then:
output = {"hookSpecificOutput": {"hookEventName": "PostToolUse",
"additionalContext": f"Security warnings: {'; '.join(warnings)}"}}
print(json.dumps(output))
PostToolUse Hooks können die Ausgabe des Tools auch vollständig ersetzen über updatedToolOutput. Claude sieht den ersetzten Inhalt statt des Originals. Das funktioniert seit v2.1.121 für alle Tools (nicht nur MCP):
import json, sys
data = json.load(sys.stdin)
original = data.get("tool_result", "")
sanitized = original.replace("/home/user", "~")
output = {"hookSpecificOutput": {"updatedToolOutput": sanitized}}
print(json.dumps(output))
Das Blockieren gefährlicher Befehle nutzt PreToolUse mit einer Regex-Prüfung und Exit-Code 2:
BLOCKED = [(r"\brm\s+-rf\s+/", "Blocking dangerous rm -rf /")]
for pattern, message in BLOCKED:
if re.search(pattern, command):
print(message, file=sys.stderr)
sys.exit(2)
Fortgeschritten: Prompt-Hooks und Component-Scope
Für Stop und SubagentStop -Ereignisse verwendet Hook-Typ "prompt" verwendet ein LLM, um den Abschluss der Aufgabe zu bewerten. Das LLM liest die Konversation und gibt eine strukturierte Entscheidung zurück, ob Claude stoppen oder weiterarbeiten soll. Das ist mächtig für Aufgaben mit expliziten Abschlusskriterien:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Check: 1) Were all files modified? 2) Do tests pass? 3) Is the PR description updated? If anything is missing, explain what.",
"timeout": 30
}
]
}
]
}
}
Hook-Typ "agent" startet einen Subagent, der die Bewertung durchführt — anders als Prompt-Hooks (einzelner Turn) können Agent-Hooks Tools nutzen und mehrstufiges Reasoning durchführen. Nutze das, wenn die Prüfung das Lesen von Dateien oder das Ausführen von Befehlen erfordert.
Hooks können außerdem auf einzelne Skills und Agents beschränkt werden, über das hooks -Frontmatter-Feld. Ein PreToolUse Hook im Frontmatter eines Skills wird nur während der Ausführung dieses Skills ausgelöst:
---
name: production-deploy
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/production-safety-check.sh"
once: true
---
Das once: true Flag führt den Hook nur einmal pro Session aus statt bei jeder passenden Tool-Nutzung. Das ist nützlich für Setup-Prüfungen, die nur einmal erfolgen müssen.
Für PostToolUse Hooks speist die continueOnBlock -Konfigurationsoption den Ablehnungsgrund des Hooks als Kontext an Claude zurück und setzt den Turn fort, statt ihn zu beenden. Ohne continueOnBlock, terminiert ein blockierender PostToolUse -Hook den Turn sofort. Mit ihr sieht Claude den Grund und kann sein Vorgehen anpassen — nützlich für Lint-Prüfungen oder das Durchsetzen von Stilregeln, bei denen Claude sich selbst korrigieren statt stoppen soll.
Hooks unterstützen außerdem ein terminalSequence Feld in ihrer JSON-Ausgabe, mit dem Hooks Desktop-Benachrichtigungen, Fenstertitel und Glockensignale ausgeben können, ohne ein steuerndes Terminal zu benötigen — nützlich für Headless- oder Remote-Sessions.