Claude Code Hooks erklärt: Die deterministische Schicht um Ihren Agenten
Was sind Claude Code Hooks? Hooks sind benutzerdefinierte Shell-Befehle (dazu HTTP-Endpunkte, MCP-Tools und Modell-Prompts), die Claude Code an festen Punkten seines Lebenszyklus automatisch ausführt: vor einem Tool-Aufruf, nach einer Bearbeitung, beim Sitzungsstart, wenn Claude seine Antwort beendet.1 Während CLAUDE.md dem Modell Anweisungen gibt, denen es wahrscheinlich folgt, laufen Hooks unabhängig davon, ob das Modell kooperiert. Tippen Sie in einer beliebigen Sitzung /hooks, um jedes Lifecycle-Event und die daran gebundenen Handler zu sehen.
Die meisten Entwickler betreiben Claude Code mit zwei Kontrollschichten: Berechtigungen, die steuern, was der Agent darf, und CLAUDE.md, das beschreibt, was er tun sollte. Hooks sind die dritte Schicht — und die einzige, die überhaupt etwas garantiert. Nachfolgend: das mentale Modell, jedes Lifecycle-Event der aktuellen Dokumentation, der exakte Vertrag für Ein- und Ausgabe, die Konfiguration, fünf funktionierende Muster und ein Entscheidungsrahmen. Jedes API-Detail wurde am 8. August 2026 gegen die offizielle Hooks-Referenz und den Leitfaden geprüft — dieses System bewegt sich schnell, wo also dieser Beitrag und die Referenz sich widersprechen, gewinnt die Referenz. (Neu bei Claude Code? Beginnen Sie mit der 5-Minuten-Einrichtung oder dem Einstiegspfad für Claude Code.)
Kurzfassung: Hooks empfangen JSON über stdin und antworten mit Exit-Codes oder JSON über stdout. Exit 0 erlaubt, Exit 2 blockiert (bei den Events, die blockieren können), und Exit 1 — der übliche Unix-Fehlercode — blockiert gar nichts, was die mit Abstand größte Stolperfalle bei Hooks ist.2 Konfiguriert werden sie in settings.json unter Event-Namen wie PreToolUse und Stop, gefiltert über Matcher. Nutzen Sie Hooks für alles, was immer passieren muss; nutzen Sie CLAUDE.md für alles, was das Modell lediglich wissen sollte.
Das mentale Modell: Garantien um einen nichtdeterministischen Kern
Ein Coding-Agent ist ein probabilistisches System. Bitten Sie ihn, nach jeder Bearbeitung Prettier auszuführen, und er wird es tun — meistens. Vielleicht überspringt er den Schritt, wenn die Änderung trivial wirkt, wenn der Kontext lang wird oder wenn Ihre Formulierung anders ankommt. CLAUDE.md, Skills und Prompts sind allesamt Vorschläge: hochwertig, meist befolgt, nie garantiert.
Hooks sind die deterministische Hülle um diesen Kern. Der Leitfaden beginnt mit einer einzeiligen Definition – „Hooks sind benutzerdefinierte Shell-Befehle.” – und benennt den Zweck unmissverständlich: Hooks geben Ihnen „deterministische Kontrolle: Bestimmte Aktionen finden immer statt, statt darauf zu vertrauen, dass das LLM sich für ihre Ausführung entscheidet.”3 (Die einzelne Zeile des Leitfadens verkauft die heutige Oberfläche unter Wert; die ausführlichere Definition der Referenz ergänzt bereits HTTP-Endpunkte und LLM-Prompts, und Handler gibt es zudem als MCP-Tools – behandelt weiter unten unter Konfiguration.) Der Formatter läuft bei jeder Bearbeitung. Die Befehlssperre bewertet jeden Bash-Aufruf. Das Abschluss-Gate prüft jedes Fertigwerden.
Die Durchsetzung ist echt, nicht kosmetisch: PreToolUse-Hooks feuern vor jeder Prüfung des Berechtigungsmodus, sodass ein Hook, der permissionDecision: "deny" zurückgibt, das Tool selbst im Modus bypassPermissions oder unter --dangerously-skip-permissions blockiert. Umgekehrt gilt das nicht — ein Hook, der "allow" zurückgibt, kann Deny-Regeln aus den Einstellungen nicht lockern. Hooks können die Richtlinie über das hinaus verschärfen, was Berechtigungen erlauben, sie aber nie aufweichen.4
Der Lebenszyklus: jedes Hook-Event
Stand 8. August 2026 dokumentiert die Referenz 31 Hook-Events.1 Sie verteilen sich auf drei Taktungen: einmal pro Sitzung (SessionStart, SessionEnd), einmal pro Zug (UserPromptSubmit, Stop, StopFailure) und bei jedem Tool-Aufruf innerhalb der agentischen Schleife (PreToolUse, PostToolUse). Der Rest feuert unter bestimmten Bedingungen — Konfigurationsänderungen, Kompaktierung, Subagenten, MCP-Interaktionen.
| Event | Feuert | Ein realer Einsatz |
|---|---|---|
SessionStart |
Sitzung beginnt oder wird fortgesetzt | Git-Branch und offene Issues als Kontext einspeisen |
Setup |
--init-only oder --init/--maintenance im -p-Modus |
Abhängigkeiten in der CI installieren, bevor der Agent läuft |
UserPromptSubmit |
Sie senden einen Prompt ab, bevor Claude ihn verarbeitet | Das aktuelle Datum anhängen; Prompts mit Geheimnissen ablehnen |
UserPromptExpansion |
Ein getippter Befehl wird zu einem Prompt expandiert | Skill- und Befehlsexpansionen prüfen oder untersagen |
PreToolUse |
Bevor ein Tool-Aufruf ausgeführt wird | Destruktive Shell-Befehle blockieren |
PermissionRequest |
Ein Berechtigungsdialog erscheint | Vertrauenswürdige Befehle automatisch freigeben, damit Sie nicht gefragt werden |
PermissionDenied |
Der Klassifikator des Auto-Modus verweigert einen Tool-Aufruf | retry: true zurückgeben, damit das Modell es erneut versuchen darf |
PostToolUse |
Nach einem erfolgreichen Tool-Aufruf | Jede bearbeitete Datei automatisch formatieren |
PostToolUseFailure |
Nach einem fehlgeschlagenen Tool-Aufruf | Fehlgeschlagene Befehle für die Fehlersuche protokollieren |
PostToolBatch |
Nach einem Bündel paralleler Tool-Aufrufe, vor dem nächsten Modellaufruf | Die agentische Schleife sichern oder anhalten |
Notification |
Claude Code sendet eine Benachrichtigung | Desktop-Hinweis, wenn Claude eine Eingabe braucht |
MessageDisplay |
Während der Text einer Assistenznachricht angezeigt wird | Auf dem Bildschirm schwärzen (nur Anzeige; das Transkript bleibt unverändert) |
SubagentStart |
Ein Subagent wird gestartet | Kontext passend zum Agententyp einspeisen |
SubagentStop |
Ein Subagent ist fertig | Die Ausgabe des Subagenten prüfen, bevor sie zurückgeht |
TaskCreated |
Eine Aufgabe wird über TaskCreate angelegt |
Regeln für Benennung oder Umfang von Aufgaben durchsetzen |
TaskCompleted |
Eine Aufgabe wird als erledigt markiert | Abnahmekriterien prüfen, bevor der Abschluss Bestand hat |
Stop |
Claude beendet seine Antwort | Abschluss-Gate: das Fertigwerden blockieren, bis die Tests bestehen |
StopFailure |
Der Zug endet wegen eines API-Fehlers | Bei rate_limit oder billing_error alarmieren (nur Protokoll; Ausgabe wird ignoriert) |
TeammateIdle |
Ein Teammitglied eines Agenten-Teams wird gleich untätig | Teammitglieder über eine Warteschlange in Arbeit halten |
InstructionsLoaded |
Eine CLAUDE.md- oder .claude/rules/*.md-Datei wird in den Kontext geladen |
Protokollieren, welche Anweisungen in die Sitzung gelangt sind |
ConfigChange |
Eine Konfigurationsdatei ändert sich mitten in der Sitzung | Unbefugte Änderungen an den Einstellungen blockieren |
CwdChanged |
Das Arbeitsverzeichnis wechselt | Umgebungen im direnv-Stil neu laden |
DirectoryAdded |
Ein Arbeitsverzeichnis wird mitten in der Sitzung über /add-dir oder das SDK-register_repo_root registriert (ab v2.1.219) |
Den Kontext dieses Repos laden, sobald es zur Sitzung stößt |
FileChanged |
Eine überwachte Datei ändert sich auf der Festplatte | Umgebungsvariablen aktualisieren, wenn sich .env ändert |
WorktreeCreate |
Ein Worktree wird über --worktree oder isolation: "worktree" erstellt |
Die Standardbereitstellung von Git-Worktrees ersetzen |
WorktreeRemove |
Ein Worktree wird entfernt | Eigene Aufräumarbeiten beim Ende der Sitzung oder des Subagenten |
PreCompact |
Vor der Kompaktierung des Kontexts | Zustand sichern, dessen Verlust Sie sich nicht leisten können |
PostCompact |
Nach abgeschlossener Kompaktierung | Kritischen Kontext erneut einspeisen |
Elicitation |
Ein MCP-Server fordert eine Benutzereingabe an | Formulare in Headless-Läufen automatisch ausfüllen |
ElicitationResult |
Nachdem Sie eine MCP-Abfrage beantwortet haben | Die Antwort prüfen oder überschreiben, bevor sie zurückgeht |
SessionEnd |
Die Sitzung endet | Logs archivieren, Ressourcen abbauen |
Die meisten davon werden Sie nie brauchen. Nahezu jedes produktive Setup besteht aus fünf: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart und Stop. Der Rest existiert für den Tag, an dem Sie ihn brauchen.
Der Vertrag: JSON hinein, Exit-Codes oder JSON hinaus
Befehls-Hooks empfangen JSON über stdin und antworten über Exit-Codes, stdout und stderr. (HTTP-Hooks erhalten dasselbe JSON als POST-Body und antworten über den Response-Body.)2
Jedes Event liefert einen gemeinsamen Rahmen — session_id, transcript_path, cwd und hook_event_name, bei den meisten Events dazu permission_mode — plus eventspezifische Felder. Ein PreToolUse-Hook für einen Bash-Befehl erhält:
{
"session_id": "abc123",
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
"cwd": "/home/user/my-project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "npm test" }
}
Andere Events tauschen den Schluss aus: UserPromptSubmit trägt prompt, SessionStart trägt source (startup/resume/clear/compact/fork – der fünfte Wert kam mit geforkten Sitzungen in v2.1.214 hinzu, und ein Hook, der auf source matcht und aus einer älteren Vierer-Liste kopiert wurde, übersieht Forks stillschweigend), Stop trägt stop_hook_active und last_assistant_message. Hooks, die innerhalb von Subagenten feuern, erhalten zusätzlich agent_id und agent_type.2
Exit-Codes
Drei Ausgänge:2
- Exit 0 — Erfolg. Claude Code parst stdout nach JSON-Ausgabefeldern. Bei den meisten Events geht stdout nur ins Debug-Log; bei
UserPromptSubmit,UserPromptExpansionundSessionStartwird einfaches stdout als Kontext ergänzt, den Claude sehen kann. - Exit 2 — blockierender Fehler. Stdout (samt jeglichem JSON) wird ignoriert; stderr wird Claude als Fehlermeldung zurückgespielt. Was „blockieren” bedeutet, hängt vom Event ab.
- Jeder andere Exit-Code — nicht blockierender Fehler. Im Transkript erscheint der Hinweis
<hook name> hook error, und die Ausführung läuft weiter.
Die letzte Zeile verdient Fettdruck: Exit 1 blockiert überhaupt nichts. Die Dokumentation warnt ausdrücklich davor — Claude Code behandelt Exit 1 als nicht blockierenden Fehler und macht weiter, obwohl 1 der übliche Unix-Fehlercode ist. Richtlinien-Hooks müssen exit 2 verwenden.2
Was Exit 2 je Event bewirkt:2
| Event | Wirkung von Exit 2 |
|---|---|
PreToolUse |
Blockiert den Tool-Aufruf |
PermissionRequest |
Verweigert die Berechtigung |
UserPromptSubmit |
Blockiert die Verarbeitung und löscht den Prompt |
UserPromptExpansion |
Blockiert die Expansion |
Stop / SubagentStop |
Verhindert das Anhalten; das Gespräch läuft weiter |
TeammateIdle |
Verhindert, dass das Teammitglied untätig wird |
TaskCreated / TaskCompleted |
Macht die Erstellung rückgängig / verhindert den Abschluss |
ConfigChange |
Blockiert die Konfigurationsänderung (außer policy_settings) |
PreCompact |
Blockiert die Kompaktierung |
PostToolBatch |
Stoppt die agentische Schleife vor dem nächsten Modellaufruf |
Elicitation / ElicitationResult |
Verweigert die Abfrage / verwandelt die Antwort in eine Ablehnung |
WorktreeCreate |
Jeder Exit-Code ungleich null bricht die Erstellung des Worktrees ab |
Alles Übrige kann nicht blockieren. PostToolUse und PostToolUseFailure zeigen Claude stderr (das Tool lief bereits); SessionStart, Notification, SessionEnd, CwdChanged, FileChanged, PostCompact, SubagentStart und Setup zeigen stderr nur dem Benutzer, während DirectoryAdded stderr allein ins Debug-Log schickt; StopFailure, InstructionsLoaded, MessageDisplay und PermissionDenied ignorieren den Exit-Code — bei PermissionDenied ist der einzige Hebel das JSON-Feld retry: true.2
JSON-Ausgabe
Für feinere Steuerung als Blockieren-oder-Schweigen: mit Exit 0 enden und ein JSON-Objekt auf stdout ausgeben. Eine Regel vorweg: entweder Exit-Codes oder JSON, niemals beides — JSON wird nur bei Exit 0 verarbeitet, und Exit 2 verwirft es.5
Universelle Felder wirken bei jedem Event: continue: false stoppt Claude vollständig (mit stopReason für den Benutzer sichtbar), suppressOutput blendet stdout aus dem Transkript aus, systemMessage zeigt dem Benutzer eine Warnung, und terminalSequence gibt eine freigegebene Terminal-Escape-Sequenz aus (Desktop-Benachrichtigung, Fenstertitel, Signalton). Entscheidungsfelder sind eventspezifisch:5
| Events | Entscheidungsmuster | Wichtige Felder |
|---|---|---|
UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact |
decision auf oberster Ebene |
decision: "block" + reason (wird Claude gezeigt). Ohne decision ist es erlaubt |
PreToolUse |
hookSpecificOutput |
permissionDecision: "allow" | "deny" | "ask" | "defer", dazu permissionDecisionReason und updatedInput, um Tool-Argumente vor der Ausführung umzuschreiben |
PermissionRequest |
hookSpecificOutput |
decision.behavior: "allow" | "deny", optional decision.updatedInput |
PermissionDenied |
hookSpecificOutput |
retry: true teilt dem Modell mit, dass es erneut versuchen darf |
PostToolUse |
hookSpecificOutput |
updatedToolOutput ersetzt das Ergebnis des Tools |
Stop / SubagentStop |
hookSpecificOutput |
additionalContext: Rückmeldung ohne Fehlercharakter, die das Gespräch fortsetzt, ohne als Hook-Fehler zu zählen |
SessionStart, Setup, SubagentStart |
Nur Kontext | additionalContext, dazu die nur für SessionStart gültigen initialUserMessage, sessionTitle, watchPaths, reloadSkills. Kein Blockieren |
MessageDisplay |
hookSpecificOutput |
displayContent ersetzt ausschließlich den Text auf dem Bildschirm |
Elicitation / ElicitationResult |
hookSpecificOutput |
action: "accept" | "decline" | "cancel", dazu content |
TeammateIdle, TaskCreated, TaskCompleted |
Universelles continue |
continue: false + stopReason stoppt den Ablauf für Teammitglied oder Aufgabe vollständig (Exit 2 ist die eventspezifische Sperre) |
WorktreeCreate |
Rückgabe eines Pfads | Befehls-Hooks geben den Worktree-Pfad auf stdout aus; HTTP-Hooks liefern hookSpecificOutput.worktreePath; ein Fehlschlag oder ein fehlender Pfad lässt die Erstellung scheitern |
WorktreeRemove, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, DirectoryAdded, FileChanged |
Keines | Nur Seiteneffekte |
Zwei Details, über die man stolpert. Erstens ist PreToolUse die Ausnahme vom Muster mit decision auf oberster Ebene: Historisch nutzte es dort decision/reason, doch beide sind für dieses Event veraltet ("approve"/"block" bilden auf "allow"/"deny" ab); verwenden Sie hookSpecificOutput.permissionDecision.5 Zweitens gilt bei widersprüchlichen PreToolUse-Hooks die Rangfolge deny > defer > ask > allow – die restriktivste Antwort gewinnt. Geben Sie dennoch jeder Entscheidung einen einzigen zuständigen Hook, statt sich auf die Auflösung des Gleichstands zu verlassen.5
Konfiguration: settings.json, Matcher, Geltungsbereich
Die Hook-Konfiguration hat drei Ebenen: ein Event auswählen, eine Matcher-Gruppe ergänzen, die filtert, wann es feuert, und einen oder mehrere Hook-Handler definieren, die laufen sollen.6
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "/path/to/lint-check.sh" }
]
}
]
}
}
Wohin Sie das schreiben, bestimmt den Geltungsbereich: ~/.claude/settings.json gilt für alle Ihre Projekte, .claude/settings.json ist projektbezogen und lässt sich einchecken, .claude/settings.local.json ist projektbezogen und wird von Git ignoriert, und die übliche Rangfolge der Einstellungen greift — verwaltete Richtlinie vor lokal vor Projekt vor Benutzer.9 Hooks können außerdem in Plugins (hooks/hooks.json) sowie im Frontmatter von Skills oder Agenten ausgeliefert werden, und Unternehmensadministratoren können verwaltete Hooks erzwingen, die Benutzer nicht überschreiben können.6
Matcher werden anhand ihrer Zeichen ausgewertet: "*", "" oder ein weggelassener Matcher trifft auf alles zu; ein Wert, der nur Buchstaben, Ziffern, _, -, Leerzeichen, Kommas und | enthält, ist eine exakte Zeichenkette oder Liste (Bash, Edit|Write); alles andere wird zu einer nicht verankerten JavaScript-Regex, sodass Edit.* sowohl Edit als auch NotebookEdit trifft — verankern Sie mit ^Edit$, wenn Sie genau ein Tool meinen. Matcher unterscheiden Groß- und Kleinschreibung, und jedes Event matcht auf sein eigenes Feld: Tool-Name bei Tool-Events, source bei SessionStart, Agententyp bei SubagentStart, Benachrichtigungstyp bei Notification.6 Für schärferes Filtern bei Tool-Events akzeptiert das Feld if pro Handler eine Berechtigungsregel wie "Bash(git *)" — allerdings nur nach bestem Bemühen (bei nicht parsbaren Befehlen fällt es offen aus), nutzen Sie für harte Garantien also Berechtigungsregeln statt if.6 Eine Semantikänderung sollten Sie kennen: Seit v2.1.214 trifft ein einsegmentiges Pfadmuster in if (etwa Edit(src/**)) nur ein src auf oberster Ebene unterhalb des Arbeitsverzeichnisses; ein vor diesem Release geschriebenes if trifft verschachtelte Pfade wie packages/app/src/ klammheimlich nicht mehr – schreiben Sie Edit(**/src/**) für das alte Verhalten über beliebige Tiefen.6
Handler gibt es in fünf Typen: command (Shell), http (POST-Endpunkt), mcp_tool, prompt (einzügige Bewertung durch das Modell) und agent (ein Subagent mit Zugriff auf Read/Grep/Glob; experimentell). Standard-Timeouts: 600 Sekunden für command/http/mcp_tool (herabgesetzt auf 30 bei UserPromptSubmit und 10 bei MessageDisplay), 30 für prompt, 60 für agent — pro Hook mit timeout überschreibbar.6 Alle passenden Hooks laufen parallel, wobei identische Handler entdoppelt werden, und $CLAUDE_PROJECT_DIR verweist Skripte auf Ihr Projektstammverzeichnis.
Prüfen Sie das mit /hooks: ein Nur-Lese-Browser, der jedes Event, dessen konfigurierte Hooks und die jeweilige Herkunftsdatei zeigt. Zum Ändern bearbeiten Sie das JSON (oder bitten Claude darum). Um vorübergehend alles stillzulegen, setzen Sie "disableAllHooks": true.6
Fünf Muster
Verallgemeinert und minimal. Das Hooks-Tutorial baut mehrere davon zu vollständigen Produktivversionen aus, und Hooks für die Apple-Entwicklung überträgt sie auf die iOS-Toolchain.
1. Automatisch formatieren nach Bearbeitungen (PostToolUse)
Direkt aus dem offiziellen Leitfaden — jede Datei, die Claude anfasst, wird formatiert, ohne Ausnahme:3
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
]
}
]
}
}
Tauschen Sie den Befehl gegen ruff format, gofmt oder swiftformat, wie es Ihr Stack verlangt.
2. Gefährliche Befehle blockieren (PreToolUse, Exit 2)
#!/bin/bash
# .claude/hooks/guard-bash.sh — register on PreToolUse, matcher "Bash"
command=$(jq -r '.tool_input.command // empty')
case "$command" in
*"rm -rf"* | *"git push --force"* | *"DROP TABLE"*)
echo "Blocked: matches a destructive pattern. Propose a safer alternative." >&2
exit 2 ;;
esac
exit 0
Exit 2 blockiert den Aufruf und spielt stderr an Claude zurück, das daraufhin den Kurs korrigiert, statt blind erneut anzusetzen. Das JSON-Äquivalent — permissionDecision: "deny" mit einer Begründung — leistet dasselbe, mit Luft nach oben für "ask" (an den Menschen eskalieren) oder updatedInput (den Befehl umschreiben).5
3. Kontext beim Sitzungsstart einspeisen (SessionStart)
Einfaches stdout aus einem SessionStart-Hook wird zu Kontext, den Claude sehen kann — ganz ohne JSON:1
#!/bin/bash
# .claude/hooks/session-context.sh — register on SessionStart
echo "Current branch: $(git branch --show-current)"
echo "Recent commits:"
git log --oneline -5
echo "Uncommitted files: $(git status --porcelain | wc -l | tr -d ' ')"
exit 0
Nutzen Sie das für dynamischen Zustand. Statische Konventionen gehören in CLAUDE.md, was die Dokumentation selbst für Kontext empfiehlt, der kein Skript erfordert.1
4. Ein Abschluss-Gate auf Stop
Stop feuert, wenn Claude seine Antwort beendet. Es zu blockieren zwingt den Agenten weiterzuarbeiten, bis eine Bedingung erfüllt ist:
#!/bin/bash
# .claude/hooks/stop-gate.sh — register on Stop
input=$(cat)
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # already continuing because of this hook; don't loop forever
fi
if ! npm test --silent > /tmp/stop-gate.log 2>&1; then
jq -n '{decision: "block", reason: "Tests are failing. Fix them before finishing. Log: /tmp/stop-gate.log"}'
fi
exit 0
Die Prüfung auf stop_hook_active ist wichtig: Claude Code deckelt einen Stop-Hook standardmäßig bei 8 aufeinanderfolgenden Sperren (anhebbar über CLAUDE_CODE_STOP_HOOK_BLOCK_CAP), und ein Gate, das nie prüft, ob es die Fortsetzung selbst ausgelöst hat, brennt geradewegs durch dieses Kontingent.7 Für sanfteres Steuern geben Sie hookSpecificOutput.additionalContext zurück statt decision: "block" — dieselbe Fortsetzung, aber als gekennzeichnete Rückmeldung statt als Hook-Fehler. Und für einmalige Bedingungen ist der eingebaute Befehl /goal ein sitzungsbezogener, prompt-basierter Stop-Hook ganz ohne Konfiguration.1
5. Der Dispatcher: ein Einstiegspunkt, viele kleine Hooks
Zehn Hooks zu registrieren heißt zehn Einträge in settings.json, die zwischen Rechnern und Projekten auseinanderdriften. Die Alternative: pro Event einen Dispatcher registrieren und per Konvention weiterleiten.
#!/bin/bash
# .claude/hooks/dispatch.sh — register once per event you care about
input=$(cat)
event=$(echo "$input" | jq -r '.hook_event_name')
dir="$CLAUDE_PROJECT_DIR/.claude/hooks/$event"
[ -d "$dir" ] || exit 0
for hook in "$dir"/*.sh; do
[ -x "$hook" ] || continue
echo "$input" | "$hook" || exit $?
done
exit 0
Eine Sperre hinzuzufügen bedeutet jetzt chmod +x auf eine neue Datei in .claude/hooks/PreToolUse/ — settings.json ändert sich nie, jedes Skript bleibt klein genug, um es isoliert zu testen, und das erste Exit 2 wird durchgereicht. Ein Vorbehalt: Der Dispatcher serialisiert, was Claude Code parallel ausführen würde, und er passt am besten zu Hooks mit Exit-Codes — ein Hook, der JSON ausgibt, sollte eigenständig bleiben, denn stdout muss genau ein JSON-Objekt enthalten.5
Hook vs. CLAUDE.md vs. Skill vs. Gedächtnis
Vier Mechanismen, vier Aufgaben:
| Mechanismus | Aufgabe | Regel für die Wahl |
|---|---|---|
| Hook | Durchsetzung | Wenn ein Überspringen unmöglich sein muss — Formatierung, Sicherheit, Gates — ist es ein Hook |
| CLAUDE.md | Anleitung | Wenn es eine Konvention ist, die das Modell in jeder Sitzung kennen sollte — Stack, Stil, Befehle — gehört es in CLAUDE.md |
| Skill | Fähigkeit | Wenn es eine Prozedur mit eigenen Anweisungen und Skripten ist, aufgerufen bei Bedarf, ist es ein Skill |
| Gedächtnis | Erinnerung | Wenn es eine Tatsache ist, die in einer Sitzung gelernt wurde und die künftige Sitzungen brauchen, ist es das Gedächtnis |
Der Fehlermodus wirkt in beide Richtungen. Konventionen als Hooks zu kodieren beschert Ihnen spröde Skripte, die etwas erzwingen, das ein Satz Anleitung mühelos abdeckt. Richtlinien als CLAUDE.md-Prosa zu kodieren beschert Ihnen einen Agenten, der genau an dem Tag mit Force-Push auf main geht, an dem es darauf ankommt. Der Test: Was kostet es, wenn das Modell das ein einziges Mal ignoriert? Ärgernis → CLAUDE.md. Zwischenfall → Hook.
Was Hooks nicht können
Ehrliche Grenzen, alle aus der offiziellen Dokumentation:7
- Hooks können keine Tools oder Slash-Befehle aufrufen. Befehls-Hooks sprechen stdout, stderr und Exit-Codes — sonst nichts. Über
additionalContextzurückgegebener Kontext wird als reiner Text eingespeist. PostToolUsekann nichts rückgängig machen. Das Tool lief bereits. Vorbeugung gehört inPreToolUse.Stopfeuert bei jedem Ende einer Antwort, nicht nur bei „Aufgabe erledigt”, und nie bei Abbrüchen durch den Benutzer (API-Fehler lösen stattdessenStopFailureaus). Gate-Logik muss Stopps mitten in der Aufgabe vertragen.PermissionRequestfeuert nicht in einfachen Headless-Läufen (-p). Es feuert sehr wohl unter-p, wenn eincanUseTool-Callback des Agent SDK die Abfrage liefert, sowie bei Tool-Aufrufen von Hintergrund-Subagenten; für alles andere Automatisierte nehmen SiePreToolUse.PreToolUsesieht keine per@referenzierten Dateien. Dateien, die Sie per@in Ihren Prompt ziehen, verursachen keinen Tool-Aufruf; schützen Sie Pfade auf diesem Weg überRead-Deny-Regeln.1- Paralleles
updatedInputist konstruktionsbedingt unzuverlässig. Wenn mehrere PreToolUse-Hooks die Argumente desselben Tools umschreiben, überlebt nur eine Umschreibung, und Sie bestimmen nicht, welche. Lassen Sie jede Umschreibung einem einzigen Hook gehören. - Timeouts brechen den Hook ab. 600 Sekunden Standard bei Befehls-Hooks (30 bei
UserPromptSubmit, 10 beiMessageDisplay); ein träges Gate, das in einen Timeout läuft, ist ein Gate, das nicht gelaufen ist. - Die Ausgabe ist auf 10.000 Zeichen begrenzt — der Überschuss wird in eine Datei geschrieben und durch eine Vorschau ersetzt.
- Hooks laufen mit Ihren vollen Benutzerrechten. Die Warnung der Referenz selbst: Sie „können alle Dateien ändern, löschen oder darauf zugreifen, auf die Ihr Benutzerkonto zugreifen kann. Prüfen und testen Sie alle Hook-Befehle, bevor Sie sie Ihrer Konfiguration hinzufügen.”8 Setzen Sie Ihre Variablen in Anführungszeichen, verwenden Sie absolute Pfade, lassen Sie sensible Dateien aus.
- Ein kaputter Hook beeinträchtigt jede Sitzung, bis er repariert ist. Debuggen Sie mit der Transkriptansicht (
Ctrl+O), mitclaude --debug-file /tmp/claude.logoder mit/debugmitten in der Sitzung; ein Klassiker ist ein Shell-Profil, das beim Start etwas ausgibt und damit die JSON-Ausgabe Ihres Hooks zerstört.7
Häufige Fragen
Was sind Claude Code Hooks?
Hooks sind benutzerdefinierte Befehle — Shell-Skripte, HTTP-Endpunkte, MCP-Tools oder Modell-Prompts —, die Claude Code an bestimmten Punkten des Lebenszyklus automatisch ausführt.3 Sie empfangen Event-JSON über stdin und antworten mit Exit-Codes oder JSON: einen Tool-Aufruf blockieren, Kontext einspeisen, Argumente umschreiben, den Agenten weiterarbeiten lassen. Anders als Anweisungen in CLAUDE.md werden sie jedes Mal ausgeführt, unabhängig vom Verhalten des Modells.
Worin unterscheiden sich PreToolUse-Hooks von Berechtigungen?
Berechtigungsregeln sind deklarativ: statische Allow-/Deny-/Ask-Muster, die Claude Code selbst auswertet. PreToolUse-Hooks sind programmierbar: Ihr Code inspiziert die vollständige Tool-Eingabe und entscheidet. Hooks feuern vor den Prüfungen des Berechtigungsmodus, deshalb hält ein "deny" aus einem Hook selbst im Modus bypassPermissions — ein "allow" aus einem Hook kann eine Deny-Regel aus den Einstellungen jedoch nicht aushebeln.4 Nutzen Sie Berechtigungsregeln für alles, was sich als Muster ausdrücken lässt; greifen Sie zum Hook, wenn die Entscheidung Logik, externen Zustand oder das Umschreiben der Eingabe braucht.
Funktionieren Hooks im Headless-Modus (-p)?
Ja — mit einer Feinheit: PermissionRequest-Hooks überspringen einfache -p-Läufe (dort liefert nichts eine Berechtigungsabfrage), sie feuern aber sehr wohl, wenn ein canUseTool-Callback des Agent SDK eine liefert, sowie bei Tool-Aufrufen von Hintergrund-Subagenten. Automatisierte Berechtigungsentscheidungen für einfache Headless-Läufe gehören in PreToolUse.7 Der Headless-Modus schaltet zudem eine Option frei, die interaktive Sitzungen ignorieren: permissionDecision: "defer", das einen Tool-Aufruf pausiert, damit ein umgebender Prozess (eine Agent-SDK-Anwendung, eine eigene Oberfläche) eine Eingabe einsammeln und die Sitzung später fortsetzen kann.5
Warum läuft mein Hook, blockiert aber nichts?
Fast immer eine Vertragsverletzung. Exit 1 blockiert nicht — nur Exit 2 tut das, und nur bei Events, die Blockieren unterstützen.2 JSON-Entscheidungen werden nur bei Exit 0 geparst — ein Skript, das {"decision": "block"} ausgibt und danach mit 2 endet, verliert sein JSON. Und Matcher unterscheiden Groß- und Kleinschreibung — bash trifft niemals Bash. Bestätigen Sie die Registrierung mit /hooks und testen Sie dann, indem Sie Beispiel-JSON in das Skript pipen und echo $? prüfen.7
Quellen
Am 8. August 2026 gegen die offizielle Dokumentation geprüft. Die Hooks-API hat sich über die Releases von Claude Code v2.1.x hinweg deutlich verändert (neue Events, neue Felder, neue Matcher-Semantik); behandeln Sie versionsabhängige Details daher als Stand genau dieses Datums.
Verwandtes auf dieser Website: der Hooks-Abschnitt des Claude Code Guides für die Gesamtsicht inklusive Prompt- und Agent-Hooks, das Hooks-Tutorial für fünf produktive Umsetzungen mit vollständigen Konfigurationen, Hooks für die Apple-Entwicklung für die angewandten iOS-Muster und der Schnelleinstieg, falls Sie Claude Code noch nicht installiert haben.
-
Anthropic, „Hooks reference — Hook lifecycle and hook events.” code.claude.com/docs/en/hooks#hook-events ↩↩↩↩↩↩
-
Anthropic, „Hooks reference — Hook input and output; exit code output; exit code 2 behavior per event.” code.claude.com/docs/en/hooks#exit-code-output ↩↩↩↩↩↩↩↩
-
Anthropic, „Automate actions with hooks.” code.claude.com/docs/en/hooks-guide ↩↩↩
-
Anthropic, „Hooks guide — Hooks and permission modes.” code.claude.com/docs/en/hooks-guide#hooks-and-permission-modes ↩↩
-
Anthropic, „Hooks reference — JSON output and decision control.” code.claude.com/docs/en/hooks#json-output ↩↩↩↩↩↩↩
-
Anthropic, „Hooks reference — Configuration: hook locations, matcher patterns, hook handler fields, das Menü /hooks.” code.claude.com/docs/en/hooks#configuration ↩↩↩↩↩↩↩
-
Anthropic, „Hooks guide — Limitations and troubleshooting.” code.claude.com/docs/en/hooks-guide#limitations-and-troubleshooting ↩↩↩↩↩
-
Anthropic, „Hooks reference — Security considerations.” code.claude.com/docs/en/hooks#security-considerations ↩
-
Anthropic, „Claude Code settings.” code.claude.com/docs/en/settings ↩