In einer Claude Code Session werden 30 Hook-Events ausgelöst. Nur 3 können antworten.

Die vollständige Liste der Claude Code Hook-Events, wann jedes davon ausgelöst wird, welche 15 blockieren können, und die stdout-Regel, die die Ausgabe der meisten Hooks stillschweigend verschluckt. Eine Praxisreferenz aus dem Produktivbetrieb von Hooks über Tausende von Agenten-Sessions.

Zwei Fehlerbilder tauchen immer wieder auf, wenn man Hooks in Claude Code einhängt, und sie sehen einander überhaupt nicht ähnlich.

Das erste: Sie fügen einen Hook hinzu, es passiert nichts. Keine Fehlermeldung, keine Warnung, keine Log-Zeile. Der Hook läuft schlicht nie.

Das zweite: Der Hook läuft offensichtlich, seine Nebenwirkungen sind auf der Platte sichtbar, aber die Nachricht, die er für den Agenten ausgibt, kommt nie an. Der Agent verhält sich, als hätte der Hook nichts gesagt.

Beides kommt aus derselben Ecke. Das Hook-System ist größer und weniger einheitlich als die Handvoll Events, die die meisten Artikel abdecken, und die Regeln darüber, wer mit dem Agenten sprechen darf, sind nicht die, die man vermuten würde. Das hier ist die Referenz, die wir uns gewünscht hätten. Wir bauen AgentsRoom auf genau diesen Hooks auf, und alles Folgende ist entweder aus der offiziellen Referenz zitiert oder in Produktion gemessen.

Es gibt 30 Events, nicht sechs

Die meisten Guides behandeln PreToolUse, PostToolUse, UserPromptSubmit, Stop, Notification und SubagentStop. Diese sechs gibt es wirklich, und sie tragen den Großteil der nützlichen Arbeit. Sie sind auch nur ein Fünftel dessen, was existiert.

Die vollständige Liste, gruppiert nach dem, was sie beobachten:

GruppeEvents
SessionSessionStart, SessionEnd, Setup
PromptUserPromptSubmit, UserPromptExpansion
ToolsPreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch
BerechtigungenPermissionRequest, PermissionDenied
TurnStop, StopFailure
Subagenten und TasksSubagentStart, SubagentStop, TaskCreated, TaskCompleted, TeammateIdle
KontextPreCompact, PostCompact, InstructionsLoaded
UmgebungFileChanged, CwdChanged, ConfigChange
WorktreesWorktreeCreate, WorktreeRemove
OberflächeNotification, MessageDisplay
MCP-ElicitationElicitation, ElicitationResult

Zeitleiste der 30 Claude Code Hook-Events in der Reihenfolge, in der sie während einer Agenten-Session ausgelöst werden, von SessionStart über UserPromptSubmit, PreToolUse, PostToolUse und Stop bis SessionEnd, mit Angabe der Events, die den Agenten blockieren können.

Die Reihenfolge, in der Events innerhalb einer einzelnen Session ausgelöst werden. Der Tool-Block wiederholt sich einmal pro Tool-Aufruf, der gesamte Prompt-Block einmal pro Turn.

Ein paar davon ändern, wie man über das System denkt. PostToolUseFailure existiert, die Verzweigung „hat das Tool funktioniert“ ist also ein Event und nichts, was Sie aus einem Payload ableiten müssen. PostToolBatch wird einmal ausgelöst, nachdem ein Batch paralleler Tool-Aufrufe abgeschlossen ist, und das ist die richtige Stelle, um einen Linter einmal statt einmal pro Edit laufen zu lassen. InstructionsLoaded wird ausgelöst, wenn CLAUDE.md gelesen wird, und gibt Ihnen damit einen Aufhängepunkt, um zu prüfen, ob der Agent wirklich die Regeln geladen hat, von denen Sie das annehmen.

Die stdout-Regel, die die Ausgabe der meisten Hooks verschluckt

Das ist der nützlichste Punkt auf dieser Seite.

Bei Exit-Code 0 parst Claude Code stdout nach JSON-Ausgabefeldern. Ob dieses stdout dem Agenten aber jemals gezeigt wird, hängt vom Event ab, und die Ausnahmen sind eine kurze Liste. Aus der Referenz:

Bei den meisten Events wird stdout ins Debug-Log geschrieben, aber nicht im Transkript angezeigt. Die Ausnahmen sind UserPromptSubmit, UserPromptExpansion und SessionStart, wo stdout als Kontext hinzugefügt wird, den Claude sehen und verwerten kann.

Drei Events von dreißig. Wenn Sie aus einem PostToolUse-Hook ein echo "Warnung: diese Migration ist destruktiv" absetzen und erwarten, dass der Agent es liest, wird er es nie tun. Ihr Text ist im Debug-Log gelandet.

Es gibt genau zwei Wege, um von einem beliebigen anderen Event aus Text vor den Agenten zu bringen:

  1. Mit Exit 2 beenden und auf stderr schreiben. Bei Exit 2 ignoriert Claude Code stdout und jedes JSON darin und gibt stderr als Fehlermeldung an den Agenten zurück.
  2. Mit Exit 0 beenden und ein JSON-Objekt ausgeben, das hookSpecificOutput.additionalContext trägt.

Beachten Sie die Asymmetrie im ersten Punkt. Exit 0 heißt: stdout zählt, stderr nicht. Exit 2 heißt: stderr zählt, stdout wird komplett verworfen. Das zu verwechseln ist der Grund, warum ein Hook völlig korrekt aussehen und trotzdem stumm bleiben kann.

Jeder andere Exit-Code ist ein nicht blockierender Fehler. Im Transkript erscheint ein Hinweis <hook name> hook error mit der ersten Zeile von stderr, die Ausführung läuft weiter, und das vollständige stderr landet im Debug-Log.

Diagramm der Exit-Codes von Claude Code Hooks: Exit 0 schickt das JSON aus stdout nur bei drei Events an den Agenten, Exit 2 blockiert die Aktion und schickt stderr an den Agenten, jeder andere Exit-Code ist ein nicht blockierender Fehler, der ins Debug-Log geschrieben wird.

Welcher Kanal den Agenten erreicht, je nach Exit-Code. Der gestrichelte Pfad ist der, den alle vermuten und den es nicht gibt.

Genau die Hälfte kann blockieren

Fünfzehn Events stoppen die Aktion bei Exit 2. Fünfzehn ignorieren ihn und machen weiter.

Können blockieren: PreToolUse, PermissionRequest, UserPromptSubmit, UserPromptExpansion, Stop, SubagentStop, TeammateIdle, TaskCreated, TaskCompleted, ConfigChange, PostToolBatch, PreCompact, Elicitation, ElicitationResult, WorktreeCreate.

Können nicht blockieren: PostToolUse, PostToolUseFailure, PermissionDenied, StopFailure, Notification, SubagentStart, SessionStart, Setup, SessionEnd, CwdChanged, FileChanged, PostCompact, WorktreeRemove, InstructionsLoaded, MessageDisplay.

Die praktische Konsequenz: Ein Schutzmechanismus gehört auf PreToolUse, nie auf PostToolUse. PostToolUse wird ausgelöst, nachdem das Tool erfolgreich war. Dort mit 2 zu beenden macht den Schreibvorgang nicht rückgängig, es gibt nur einen Fehler aus, während der Schaden schon auf der Platte liegt. Wenn Sie ein rm -rf stoppen wollen, haben Sie genau eine Stelle dafür.

Dass PostToolBatch blockieren kann und PostToolUse nicht, ist einen zweiten Blick wert. Es bedeutet, dass eine Prüfung auf Batch-Ebene den Turn noch stoppen kann, nachdem parallele Edits geschrieben wurden: das ist das Nächste an einem Veto nach dem Schreiben, was das System zu bieten hat.

Matcher sind exakt, bis sie es plötzlich nicht mehr sind

Das Matcher-Feld wechselt seine Auswertungsstrategie abhängig von seinen eigenen Zeichen, und nichts sagt Ihnen, welchen Weg es genommen hat.

MatcherAusgewertet als
"*", "", oder weggelassenpasst auf alles
Nur Buchstaben, Ziffern, _, -, Leerzeichen, ,, |exakte Zeichenkette, oder Liste exakter Zeichenketten, getrennt an | oder ,
Alles anderenicht verankerter regulärer JavaScript-Ausdruck

Die Falle ist „nicht verankert“. Die Referenz sagt ausdrücklich, dass der reguläre Ausdruck mit RegExp.prototype.test geprüft wird, was bei einem Treffer an beliebiger Stelle im Wert anschlägt. Also passt Edit.* auf Edit und auf NotebookEdit. Wenn Sie ein einzelnes Tool meinten, schreiben Sie ^Edit$.

Zwei versionsabhängige Verhaltensweisen, die man kennen sollte, bevor man am falschen Ende debuggt:

  • Kommas als Trennzeichen und die Toleranz gegenüber Leerzeichen brauchen Claude Code v2.1.191 oder neuer.
  • Bindestriche kamen erst in v2.1.195 zum Zeichensatz der exakten Übereinstimmung dazu. Davor wurde ein Matcher wie code-reviewer als nicht verankerte Regex behandelt und griff damit auch bei senior-code-reviewer.

In AgentsRoom grenzen wir unseren eigenen Hook zur Datei-Zuordnung mit Write|Edit|MultiEdit|NotebookEdit ein, was auf dem Weg der exakten Zeichenkette bleibt und genau diese vier Tools trifft und sonst nichts. Die Lifecycle-Hooks, die wir installieren, tragen überhaupt keinen Matcher, weil sie uns immer betreffen.

Sechs Stellen können Hooks definieren, und sie werden zusammengeführt

Der Reflex ist, nach einer Rangfolge zu suchen. Es gibt keine, und genau das ist der interessante Teil.

OrtGeltungsbereich
~/.claude/settings.jsonalle Ihre Projekte, lokal auf Ihrer Maschine
.claude/settings.jsonein Projekt, commitbar
.claude/settings.local.jsonein Projekt, von Claude Code auf gitignore gesetzt
Managed policy settingsorganisationsweit, vom Admin kontrolliert
Plugin hooks/hooks.jsonsolange das Plugin aktiviert ist
Frontmatter von Skill oder Agentsolange die Komponente aktiv ist

Aus der Referenz:

Hook-Einträge werden über die Settings-Ebenen hinweg zusammengeführt, statt sich gegenseitig zu ersetzen: User-, Projekt- und lokale Settings fügen ihre eigenen Hooks hinzu, ohne die verwalteten zu entfernen, und die Einstellung disableAllHooks kann verwaltete Hooks nicht von außerhalb der verwalteten Settings deaktivieren.

Ein Projekt-Hook überschreibt also nie einen globalen, er legt sich obendrauf. Sechs Quellen, alle additiv. Ein PostToolUse-Formatter, der in Ihren User-Settings und noch einmal im Projekt definiert ist, läuft zweimal pro Edit, und das einzige Symptom ist, dass sich alles zäh anfühlt.

Diagramm der sechs Claude Code Settings-Orte, die Hooks definieren können, und wie sie sich alle additiv zu einem einzigen Satz von Hooks zusammenfügen, statt sich gegenseitig zu überschreiben.

Sechs Quellen, ein zusammengeführter Satz. Nichts hier überschreibt irgendetwas.

Das erklärt auch, warum .claude/settings.local.json der richtige Ort ist, wenn ein Werkzeug einen Hook in das Projekt eines anderen installiert. Sie ist projektbezogen, Claude Code setzt sie auf gitignore, und sie wird ohne jedes CLI-Flag geladen. Dorthin schreibt AgentsRoom seine Einträge, damit die eingecheckte .claude/settings.json des Nutzers nie angefasst wird und seine Kollegen nie einen maschinenspezifischen Pfad erben.

Was uns der Produktivbetrieb von Hooks beigebracht hat

AgentsRoom installiert Hooks in jedes Projekt, das es öffnet, um den Agentenstatus deterministisch zu verfolgen und bearbeitete Dateien dem richtigen Agenten zuzuordnen. Ein paar Dinge zeigen sich erst in dieser Größenordnung.

Unbekannte Event-Namen werden stillschweigend ignoriert. Das steht nicht in der Dokumentation, und wir verlassen uns darauf. Wenn wir unserem Installer ein neues Lifecycle-Event hinzufügen, bekommen Nutzer mit einer älteren CLI eine settings.local.json mit einem Event-Namen, von dem ihr Binary noch nie gehört hat. Nichts bricht, nichts warnt, der Eintrag wird übersprungen. Genau das macht es sicher, den Installer vor einem CLI-Release auszuliefern. Und genau das ist zwangsläufig auch der Grund, warum ein Tippfehler völlige Stille erzeugt statt einer Fehlermeldung.

An agent_id erkennen Sie, dass Sie sich in einem Subagenten befinden. Das Feld ist nur vorhanden, wenn der Hook innerhalb eines Subagenten-Aufrufs ausgelöst wird. Das wiegt schwerer, als es klingt: Stop wird ausgelöst, wenn ein Subagent seinen Turn beendet, nicht nur der Hauptagent. Eine naive Regel nach dem Muster „Session bei Stop als erledigt markieren“ markiert die gesamte Session als fertig, sobald der erste beliebige Subagent zurückkehrt. Genau deshalb überspringen wir Turn-Events, die agent_id tragen.

Lesen Sie transcript_path nicht für den laufenden Turn. Die Referenz warnt, dass das Transkript asynchron geschrieben wird und der Konversation im Speicher hinterherhinken kann, die neuesten Nachrichten also möglicherweise noch nicht da sind, wenn Ihr Hook ausgelöst wird. Stop und SubagentStop bekommen last_assistant_message genau dafür, damit Sie nie mit der Datei um die Wette laufen müssen.

Hooks sind das einzige verlässliche Statussignal. Vor den Hooks haben wir das PTY ausgelesen, um herauszufinden, ob ein Agent gerade denkt, wartet oder fertig ist. Das bricht in dem Moment, in dem die CLI über den alternativen Bildschirmpuffer des Terminals rendert, und genau das macht /tui fullscreen. Hooks werden unter jedem Renderer identisch ausgelöst. Wenn Sie irgendetwas bauen, das einen Agenten von außen beobachtet, ist das die Schicht, auf der Sie aufsetzen sollten, und das Auslesen des Terminals bleibt bestenfalls ein Fallback.

async: true kostet nichts. Ein Hook-Kommando kann async: true deklarieren, und der Agent wartet nicht darauf. Unser Hook schickt ein POST an einen lokalen Endpunkt mit einer Obergrenze von 2 Sekunden und kehrt zurück; die Turn-Latenz des Agenten bleibt unberührt, selbst wenn die empfangende App geschlossen ist. Wenn Ihr Hook nur beobachtet und nie entscheidet, machen Sie ihn async und hören Sie auf, dafür zu bezahlen.

Lassen Sie einen Hook nie Müll ins Terminal schreiben. Unser Skript schluckt jede Exception, auch auf oberster Ebene. Ein unbehandelter Traceback aus einem Hook scheitert nicht einfach leise, er druckt einen Python-Stacktrace mitten in die Terminal-Session des Nutzers, während dieser arbeitet.

Timeouts

Die Standardwerte sind großzügig, mit drei Ausnahmen, die es nicht sind:

Hook-TypStandard-Timeout
command, http, mcp_tool600 s
prompt30 s
agent60 s
UserPromptSubmit (command, http, mcp_tool)30 s
MessageDisplay (command, http, mcp_tool)10 s
SessionEnd1,5 s geteilt über alle Hooks, angehoben auf ein längeres timeout pro Hook, bis zu 60 s

Das Budget von SessionEnd ist das, was die Leute überrascht. Es ist ein geteiltes Budget, keine Zuteilung pro Hook: Drei Cleanup-Hooks teilen sich also 1,5 Sekunden untereinander auf, sofern Sie es nicht ausdrücklich anheben.

Die Kurzfassung

  • Es gibt 30 Events. Sechs davon sind bekannt.
  • stdout erreicht den Agenten nur bei UserPromptSubmit, UserPromptExpansion und SessionStart. Überall sonst: Exit 2 mit stderr, oder additionalContext im JSON.
  • 15 Events blockieren bei Exit 2, 15 ignorieren ihn. Schutzmechanismen gehören auf PreToolUse.
  • Matcher sind exakte Zeichenketten, bis ein Sonderzeichen sie in eine nicht verankerte Regex verwandelt.
  • Sechs Settings-Quellen werden additiv zusammengeführt. Nichts überschreibt irgendetwas.
  • Ein falsch geschriebener Event-Name scheitert vollkommen lautlos.

Wenn Sie diese Events lieber auslösen sehen wollen, statt über sie nachzudenken: genau das haben wir gebaut. AgentsRoom zeigt jeden Hook-Trigger pro Agent, pro Projekt, pro Lauf, über Dutzende paralleler Agenten und Subagenten-Sessions hinweg. Die Hooks, die Sie in Ihren eigenen Settings konfigurieren, funktionieren weiterhin genau so, wie sie geschrieben sind, weil dahinter die echte CLI läuft.

Weiterlesen

AgentsRoom herunterladen

Führe deine KI-Agenten (Claude, Codex, Antigravity CLI, OpenCode, Aider, Grok Build, Mistral Vibe, Kimi Code) auf all deinen Projekten aus, von einem einzigen Fenster.

KostenlosAgentsRoom herunterladen

Companion-App: Agenten auch unterwegs im Blick behalten

Nutzen Sie Claude, Codex, Antigravity CLI oder einen anderen AI-Anbieter.

Erweiterung installieren
Chrome Web Store

Bugs und Wünsche direkt in dein öffentliches Backlog schicken.

Ein Blick auf AgentsRoom in Aktion.

Multi-Projekte
Multi-Provider
Multi-Agenten
Live-Status
Diff & Commit
Mobile App
Live-Vorschau
Agent-Teams
Browser-Tests
Backlog-getriebene Entwicklung
Prompt-Bibliothek
Skills-Bibliothek
Alle Funktionen ansehen