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:
| Gruppe | Events |
|---|---|
| Session | SessionStart, SessionEnd, Setup |
| Prompt | UserPromptSubmit, UserPromptExpansion |
| Tools | PreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch |
| Berechtigungen | PermissionRequest, PermissionDenied |
| Turn | Stop, StopFailure |
| Subagenten und Tasks | SubagentStart, SubagentStop, TaskCreated, TaskCompleted, TeammateIdle |
| Kontext | PreCompact, PostCompact, InstructionsLoaded |
| Umgebung | FileChanged, CwdChanged, ConfigChange |
| Worktrees | WorktreeCreate, WorktreeRemove |
| Oberfläche | Notification, MessageDisplay |
| MCP-Elicitation | Elicitation, ElicitationResult |

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,UserPromptExpansionundSessionStart, 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:
- 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.
- Mit Exit 0 beenden und ein JSON-Objekt ausgeben, das
hookSpecificOutput.additionalContextträ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.

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.
| Matcher | Ausgewertet als |
|---|---|
"*", "", oder weggelassen | passt auf alles |
Nur Buchstaben, Ziffern, _, -, Leerzeichen, ,, | | exakte Zeichenkette, oder Liste exakter Zeichenketten, getrennt an | oder , |
| Alles andere | nicht 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-reviewerals nicht verankerte Regex behandelt und griff damit auch beisenior-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.
| Ort | Geltungsbereich |
|---|---|
~/.claude/settings.json | alle Ihre Projekte, lokal auf Ihrer Maschine |
.claude/settings.json | ein Projekt, commitbar |
.claude/settings.local.json | ein Projekt, von Claude Code auf gitignore gesetzt |
| Managed policy settings | organisationsweit, vom Admin kontrolliert |
Plugin hooks/hooks.json | solange das Plugin aktiviert ist |
| Frontmatter von Skill oder Agent | solange 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
disableAllHookskann 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.

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-Typ | Standard-Timeout |
|---|---|
command, http, mcp_tool | 600 s |
prompt | 30 s |
agent | 60 s |
UserPromptSubmit (command, http, mcp_tool) | 30 s |
MessageDisplay (command, http, mcp_tool) | 10 s |
SessionEnd | 1,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,UserPromptExpansionundSessionStart. Überall sonst: Exit 2 mit stderr, oderadditionalContextim 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
Wie man KI-Coding-Agenten in einem Entwicklerteam skalieren kann
Ein Entwickler mit einem Coding-Agenten ist eine Produktivitätsgeschichte. Fünf Entwickler mit zwanzig Agenten sind ein Koordinationsproblem. Hier ist, was zuerst bricht, wenn ein Team wächst, und das Setup, das funktioniert: engagierte Kontextdateien, klare Dateieigentümerschaft, Überprüfung nach Blast-Radius und Kosten, die man tatsächlich sehen kann.
Zum ArtikelAgentsRoom unterstützt jetzt Kimi Code
Kimi Code, der Terminal-Coding-Agent von Moonshot AI, ist jetzt ein vollwertiger Provider in AgentsRoom. Lass ihn neben Claude, Codex, Antigravity CLI, OpenCode, Aider, Grok Build und Mistral Vibe laufen und wechsle mitten im Gespräch.
Zum ArtikelSollten Sie den Code Ihres KI-Agenten weiterhin überprüfen?
Ihre Agenten schreiben besseren Code als die Hälfte der Pull-Requests, die Sie früher zusammengeführt haben. Lesen Sie also immer noch jede Zeile? Der ehrliche Fall für beide Seiten, die 10 Signale, die Ihnen sagen, dass ein Agent einen Fehler gemacht hat, und wie viel Überprüfung jede Änderung tatsächlich verdient.
Zum Artikel
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.
Companion-App: Agenten auch unterwegs im Blick behalten
Nutzen Sie Claude, Codex, Antigravity CLI oder einen anderen AI-Anbieter.
Bugs und Wünsche direkt in dein öffentliches Backlog schicken.
Ein Blick auf AgentsRoom in Aktion.