Zum Hauptinhalt springen

Hooks: Deterministische Automatisierung

Experte

Hooks sind Shell-Befehle, die Claude Code automatisch ausführt an definierten Punkten seines Lebenszyklus. Während Berechtigungen entscheiden, ob eine Aktion erlaubt ist, lassen Hooks dich deterministische Logik darum herum ausführen — Formatierung, Validierung, Logging, Gates, Schwärzung. Damit wird ein Verhalten garantiert, statt dass man hofft, Claude „denkt schon daran“.

What you'll learn
  • Wann du zu einem Hook greifst statt zu einer Anweisung, einer Berechtigung oder einem Skill
  • Der vollständige Lebenszyklus: Ereignisse für Sitzung, Prompt, Tool, Subagent, Task, Kompaktierung und Turn-Ende
  • Wie ein Hook verdrahtet wird: Ereignis, Matcher und die JSON-Payload auf stdin
  • Die zwei Arten, wie ein Hook eine Aktion blockiert — Exit-Code 2 vs. JSON auf stdout
  • Die universellen JSON-Ausgabefelder, die ereignisübergreifend gelten: continue, additionalContext, systemMessage, reloadSkills, sessionTitle, suppressOutput, updatedInput
  • Die guten Praktiken und häufigen Fehler, die schnelle, sichere Hooks von trägen, stillen unterscheiden

Wann du zu einem Hook greifst​

Greife zu einem Hook, wenn ein Verhalten garantiert sein soll, nicht nur erbeten. Jede übliche Aufgabe entspricht einem Lebenszyklus-Ereignis:

  • Auto-Format / Lint nach jeder Dateiänderung (PostToolUse auf Edit|Write).
  • Eine Aktion, die gegen eine Regel verstößt, blockieren, bevor sie läuft (PreToolUse).
  • Die Nachricht des Assistenten schwärzen, bevor sie im Terminal erscheint (MessageDisplay).
  • Nach einem SessionStart Skills neu laden oder einen Sitzungstitel setzen.
  • Benachrichtigen oder loggen, wenn eine Sitzung endet, ein Task fertig wird oder ein Turn fehlschlägt (Stop, StopFailure, SessionEnd).
  • Kontext einspeisen beim Sitzungsstart oder wenn Anweisungen geladen werden.

Wie sie funktionieren​

Du registrierst Hooks in settings.json und ordnest sie einem Ereignis zu (und oft einem Matcher — meist ein Tool-Name, manchmal ein Subagent-Typ, ein Kompaktierungs-Trigger oder eine Benachrichtigungsart). Wenn das Ereignis eintritt, führt Claude deinen Befehl aus und übergibt eine JSON-Payload auf stdin (den Ereignisnamen, die Sitzung und ereignisspezifische Felder wie Tool-Name und Eingaben). Exit-Code und stdout deines Befehls entscheiden, was als Nächstes passiert.

Guided walkthrough1 of 4
  1. Registriere den Hook in settings.json unter dem Lebenszyklus-Ereignis, das dich interessiert — etwa PostToolUse für Automatisierung nach einem Tool oder MessageDisplay für Schwärzung.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
]
}
]
}
}

Der Hook oben liest den Pfad der bearbeiteten Datei aus dem JSON auf stdin (.tool_input.file_path) und formatiert sie. Nimm nicht an, dass eine Umgebungsvariable den Pfad enthält — lies ihn von stdin. Nützliche Pfad-Platzhalter wie ${CLAUDE_PROJECT_DIR} sind verfügbar, um Skripte zu finden.

Der Lebenszyklus auf einen Blick​

Claude-Code-Hooks umfassen die gesamte Sitzung, nicht nur Tool-Aufrufe. Gruppiert nach Phase siehst du, wo du deine Logik einhängen kannst.

PhaseEreignisseTypische Verwendung
SitzungSessionStart, SessionEnd, Setup, InstructionsLoaded, ConfigChange, CwdChanged, DirectoryAdded, FileChangedKontext beim Start einspeisen, am Ende einen Snapshot ziehen, auf ein Neuladen von CLAUDE.md oder eine .env-Änderung auf der Platte reagieren
PromptUserPromptSubmit, UserPromptExpansionDen Prompt prüfen oder umschreiben, bevor Claude ihn sieht
ToolPreToolUse, PermissionRequest, PermissionDenied, PostToolUse, PostToolUseFailure, PostToolBatchEine Aktion erlauben/verweigern, an einen Menschen eskalieren, nach Änderungen formatieren, nach einer Ablehnung im Auto-Modus erneut versuchen
SubagentSubagentStart, SubagentStopStart/Stopp loggen, einen Subagent am Zurückgeben hindern
TaskTaskCreated, TaskCompletedEinen versehentlichen Task zurückrollen, den Abschluss prüfen
KompaktierungPreCompact, PostCompactVor der Kompaktierung einen Snapshot des Transkripts ziehen, danach verifizieren
ModellPreModelSwitch, PostModelSwitchEinen Modellwechsel blockieren, bevor er greift; nach dem Wechsel reagieren, auch wenn eine fortgesetzte Sitzung das Modell wiederherstellt
TurnStop, StopFailure, Notification, MessageDisplayBei Abschluss benachrichtigen, bei API-Fehlern alarmieren, angezeigten Text schwärzen
Team + MCPTeammateIdle, Elicitation, ElicitationResult, WorktreeCreate, WorktreeRemoveSitzungsübergreifende Koordination, MCP-Elicitations prüfen, Worktree-Operationen umhüllen

Die genauen stdin-Felder, die Matcher-Grammatik und die Blockierregeln jedes Ereignisses stehen in der offiziellen Hooks-Referenz. Der Katalog oben ist die Karte; die Referenz ist das Gelände.

Wie ein Hook blockiert​

Zwei Wege, je nach Ereignis:

  • Exit-Code 2 — der Hook lässt die Aktion fehlschlagen, und was er auf stderr geschrieben hat, wird zur Nachricht, die Claude sieht. Einfach und für Command-Hooks auf blockierenden Ereignissen möglich (PreToolUse, UserPromptSubmit, UserPromptExpansion, Stop, SubagentStop, TeammateIdle, TaskCreated, TaskCompleted, ConfigChange, PostToolBatch, PreCompact, PreModelSwitch, Elicitation, ElicitationResult, WorktreeCreate, WorktreeRemove). Bei PermissionRequest wird Exit 2 ignoriert; verweigere stattdessen über das decision-Objekt.
  • JSON auf stdout (Exit 0) — gib eine strukturierte Entscheidung zurück. Bei PreToolUse ist das eine permissionDecision mit "allow", "deny", "ask" (den Aufruf dem Nutzer vorlegen) oder "defer" (nur für -p-Läufe, die von einer App gesteuert und wieder aufgenommen werden), mit einer permissionDecisionReason. Nicht blockierende Ereignisse (PostToolUse, Notification, MessageDisplay, StopFailure, die meisten Sitzungsereignisse) ignorieren Blockierversuche — ihre JSON-Ausgabe kann aber trotzdem steuern, was Claude als Nächstes sieht.

Das Skript unten ist ein PreToolUse-Hook auf dem Bash-Tool. Lies es von oben nach unten: Es zieht den Befehl aus stdin, und wenn er destruktiv aussieht, schreibt es einen Grund nach stderr und beendet sich mit 2, um zu blockieren.

#!/usr/bin/env bash
# PreToolUse hook on the Bash tool: refuse to delete things.
command=$(jq -r '.tool_input.command' < /dev/stdin)
if [[ "$command" == rm\ * || "$command" == *"rm -rf"* ]]; then
echo "Blocked: destructive 'rm' is not allowed by policy." >&2
exit 2
fi
exit 0

Universelle JSON-Ausgabefelder​

Die meisten Ereignisse akzeptieren auf stdout ein strukturiertes JSON-Objekt, das steuert, was als Nächstes passiert — getrennt von den ereignisspezifischen Entscheidungsfeldern wie permissionDecision. Die nützlichsten:

FeldWas es tutWo es funktioniert
continueBei false hört Claude komplett auf zu verarbeiten — hat Vorrang vor jeder ereignisspezifischen EntscheidungDie meisten Ereignisse
systemMessageFügt dem Kontext / Transkript von Claude eine Nachricht hinzu, damit es sieht, warum du eingegriffen hastDie meisten blockierenden Ereignisse
additionalContextSpeist zusätzlichen Kontext ein, den Claude im nächsten Turn lesen und nutzen kannSessionStart, UserPromptSubmit, weitere
reloadSkillsDurchsucht die Skill-Verzeichnisse neu, damit eine Sitzung einen frisch geschriebenen Skill erkenntSessionStart (und andere Sitzungsereignisse)
sessionTitleSetzt den Sitzungstitel in der UI — unter hookSpecificOutput bei SessionStartSessionStart
suppressOutputVerbirgt die Ausgabe des Tools vor Claude (nützlich bei lauten Befehlen)Tool-Ereignisse
updatedInputSchreibt die Eingabe des Tools um, bevor es läuft (z. B. ein --dry-run-Flag hinzufügen)PreToolUse
terminalSequenceGibt Terminal-Escape-Sequenzen aus — Glocke, Fenstertitel, Benachrichtigungs-BadgeNotification, StopFailure, weitere

Ereignisspezifische Entscheidungsfelder — permissionDecision ("allow", "deny", "ask" oder "defer", plus permissionDecisionReason) bei PreToolUse, decision.behavior ("allow" oder "deny") bei PermissionRequest und retry: true bei PermissionDenied, damit Claude den abgelehnten Aufruf erneut versuchen darf — setzen auf diesen universellen auf.

Das mentale Modell​

Ein PreToolUse-Hook läuft vor der Aktion und kann sie blockieren; ein PostToolUse-Hook läuft nachdem sie erfolgreich war und reagiert auf das Ergebnis.

Hook-Ereignisse auf einen Blick
Drücke Enter oder die Leertaste, um die Karte umzudrehen. Nutze die Pfeiltasten links und rechts, um zwischen den Karten zu wechseln.Begriff angezeigt.
1 / 6

Gute Praktiken​

  • Halte Hooks schnell und idempotent — sie laufen oft. SessionEnd-Hooks teilen sich ein einziges Budget von 1,5 Sekunden, also halte die Arbeit am Sitzungsende minimal.
  • Bei echten Problemen laut scheitern, aber bei kosmetischen nicht blockieren.
  • Behandle Hook-Ausgaben als Feedback an Claude — eine klare Meldung auf stderr (Exit 2) oder in systemMessage (JSON) hilft Claude, sich selbst zu korrigieren.
  • Bevorzuge JSON-Ausgabe gegenüber Exit-Codes, wenn du mehr sagen willst als „blockiert“ — permissionDecision: "ask" bei PreToolUse schiebt die Entscheidung an einen Menschen, ohne die Tür zuzuschlagen, und mit additionalContext kannst du lenken, statt nur abzulehnen.
  • Hooks laufen mit den Rechten deiner Shell — prüfe jeden Hook, den du nicht selbst geschrieben hast (Fremden Code prüfen).

Häufige Fehler​

  • Den Dateipfad aus einer Umgebungsvariable lesen. Der Pfad steht im stdin-JSON (.tool_input.file_path), nicht in $CLAUDE_FILE_PATH. Leite stdin per Pipe durch jq.
  • Stille Blockaden. Wenn ein PreToolUse-Hook mit 2 endet und nichts auf stderr steht, ist Claude blockiert, weiß aber nicht, warum, und kann sich nicht anpassen. Schreibe immer einen klaren Grund (oder nutze permissionDecisionReason).
  • Versuchen, ein nicht blockierendes Ereignis zu blockieren. PostToolUse löst aus, nachdem das Tool lief — ein Exit 2 legt Claude nur eine Nachricht ins Ohr, die Aktion lässt sich nicht rückgängig machen. Wenn du absichern willst, hänge dich an PreToolUse.
  • Langsame Hooks. Ein PostToolUse-Hook läuft nach jeder passenden Änderung. Ein Linter mit 3 Sekunden lässt die ganze Sitzung zäh wirken — halte Hooks schnell und reagiere idealerweise nur auf das, was sich geändert hat.
  • Zu breite Matcher. matcher: ".*" löst bei jedem Tool aus. Grenze ein mit einem exakten Namen, einer Edit|Write-Liste oder dem if-Feld pro Handler (z. B. "if": "Bash(git push *)").
  • Vergessen, dass es Matcher auch für Nicht-Tool-Ereignisse gibt. SessionStart matcht startup|resume|clear|compact|fork; Notification matcht Arten wie permission_prompt|idle_prompt|agent_completed; PreCompact matcht manual|auto. Ein einzelner Hook, der an der falschen Teilmenge hängt, löst entweder zu oft oder nie aus.
  • Hooks vertrauen, die du nicht geschrieben hast. Ein Hook führt beliebige Shell-Befehle mit deinen Rechten aus. Prüfe jeden Hook aus einem Plugin oder einer Vorlage zuerst — siehe Fremden Code prüfen.
Watch out
  • Ein Hook führt beliebige Shell-Befehle mit deinen Rechten aus — verdrahte nie einen Hook aus einem Plugin oder einer Vorlage, ohne ihn vorher gelesen zu haben.

Rezepte zum Abschauen​

Kopierfertige Starter findest du unter Hooks- & settings.json-Rezepte. Einige wirkungsvolle, die die Form neuerer Ereignisse zeigen:

Bearbeitete Dateien automatisch formatieren (PostToolUse auf Edit|Write)

{
"hooks": {
  "PostToolUse": [
    {
      "matcher": "Edit|Write",
      "hooks": [
        { "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
      ]
    }
  ]
}
}

Beim Start einen Sitzungstitel pro Projekt setzen (SessionStart)

{
"hooks": {
  "SessionStart": [
    {
      "matcher": "startup|resume",
      "hooks": [
        {
          "type": "command",
          "command": "jq -nc --arg t \"$(basename \"$PWD\") — $(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo detached)\" '{hookSpecificOutput:{sessionTitle:$t}}'"
        }
      ]
    }
  ]
}
}

Skills nach einem SessionStart neu laden (erkennt einen frisch geschriebenen Skill)

{
"hooks": {
  "SessionStart": [
    {
      "matcher": "startup|resume",
      "hooks": [
        { "type": "command", "command": "printf '{\"reloadSkills\": true}\n'" }
      ]
    }
  ]
}
}

API-Schlüssel im angezeigten Assistententext schwärzen (MessageDisplay)

{
"hooks": {
  "MessageDisplay": [
    {
      "hooks": [
        {
          "type": "command",
          "command": "jq -r '.message.content' | sed -E 's/(sk-[A-Za-z0-9_-]{20,})/sk-***REDACTED***/g'"
        }
      ]
    }
  ]
}
}

Bei fehlgeschlagenem Turn benachrichtigen (StopFailure — Rate-Limits, Überlastung, Auth)

{
"hooks": {
  "StopFailure": [
    {
      "matcher": "rate_limit|overloaded|authentication_failed|billing_error",
      "hooks": [
        { "type": "command", "command": "osascript -e 'display notification \"Claude Code turn failed — check the terminal\" with title \"Claude Code\"'" }
      ]
    }
  ]
}
}

Teste dich selbst

0/4
  1. Wo findet ein Hook den Pfad der Datei, die gerade bearbeitet wurde?
  2. Ein PreToolUse-Hook endet mit Exit-Code 2. Was passiert?
  3. Eine Sitzung soll einen Skill erkennen, den du gerade in .claude/skills/ hinzugefügt hast, ohne Neustart. Welches Ausgabefeld, bei welchem Ereignis?
  4. Du willst Geheimnisse im Text von Claude schwärzen, bevor er auf dem Bildschirm des Nutzers erscheint. Welches Ereignis?
Key takeaways
  • Hooks machen Verhalten garantiert statt erbeten — deterministische Logik rund um Aktionen, die Berechtigungen nur erlauben oder verweigern.
  • Der Lebenszyklus umfasst mehr als 30 Ereignisse in den Phasen Sitzung, Prompt, Tool, Subagent, Task, Kompaktierung und Turn — wähle die richtige Phase, bevor du das Skript schreibst.
  • Registriere in settings.json ein Ereignis plus Matcher; Claude leitet eine JSON-Payload per Pipe auf stdin und liest deinen Exit-Code und deine Ausgabe.
  • Lies den Dateipfad von stdin (.tool_input.file_path) — nicht aus einer Umgebungsvariable.
  • Blockiere mit Exit-Code 2 (stderr wird zur Nachricht) oder mit strukturiertem JSON auf stdout (permissionDecision bei Tool-Ereignissen, {continue: false}, um alles zu stoppen).
  • Universelle Ausgabefelder — additionalContext, systemMessage, reloadSkills, sessionTitle, suppressOutput, updatedInput — lassen dich Claude lenken, statt nur zu erlauben oder abzulehnen.
  • Halte Hooks schnell, idempotent und eng gematcht — und prüfe jeden Hook, den du nicht selbst geschrieben hast, denn er läuft mit den Rechten deiner Shell.

Weiter​