Zwei deterministische Prüfungen stellt Siddiqi vor. Sie greifen an ganz verschiedenen Stellen im Ablauf eines KI-Agenten. Die eine feuert, bevor der Agent ein Werkzeug ausführt, die andere in dem Moment, in dem er glaubt, fertig zu sein. Wer bisher auf eine AGENTS.md oder eine CLAUDE.md vertraut, um Regeln für generierten Code zu hinterlegen, kennt das Muster: Meistens hält sich der Agent daran, und genau dann nicht, wenn es teuer wird.
Seine Anweisungen im Kontextfile sind aus seiner Sicht ein Wunschzettel, der statistisch befolgt wird. Wer eine Garantie will, braucht einen Mechanismus, der nicht mit dem Modell verhandelt. Dort setzen Agent Hooks an. Der Unterschied zu dem, was viele unter einem Hook verstehen, ist größer, als der Name vermuten lässt.
Warum eine Regel in der CLAUDE.md keine Garantie ist
Zarar Siddiqi nennt zwei Fälle, die ihm konkret wehgetan haben. Erstens: Der Agent soll niemals ein rohes <input>-Tag schreiben, weil jede Formularzeile durch die eigene Design-System-Komponente laufen muss. Zweitens: Der Agent soll nicht behaupten, er sei fertig, solange ein bestimmter Ratchet-Test im Design-System rot ist. Beides sind Regeln, die man aufschreiben kann – und beides sind Regeln, die in der Praxis gelegentlich untergehen.
Das ist kein Modellfehler im engeren Sinn, sondern eine Eigenschaft von Anweisungen. Ein Satz in der CLAUDE.md konkurriert mit dem gesamten restlichen Kontext, mit dem Ziel der aktuellen Aufgabe und mit der Wahrscheinlichkeit, dass das Modell diese eine Zeile gerade für relevant hält. Meistens tut es das. Und manchmal eben nicht. Wer darauf baut, baut auf eine Quote, nicht auf eine Zusage.
Zarar Siddiqi versteht das nicht als Argument gegen Kontextdateien. Sie bleiben nützlich, um Absichten zu erklären und Zusammenhänge zu liefern. Sie taugen nur nicht als Durchsetzungsinstanz. Ein Wunsch und eine Sperre sind zwei verschiedene Dinge, auch wenn beide in derselben Datei stehen könnten.
Git-Hook und Agent-Hook: zwei Kontrollpunkte
Man kann sich das wie eine Werkhalle vorstellen. Der git pre-commit hook ist die Endkontrolle am Packtisch: Dort wird formatiert, dort läuft git pull, dort fällt auf, dass etwas nicht zusammenpasst. Nützlich, aber spät. Das Teil ist fertig gebaut, verpackt und liegt schon im Karton, wenn die Prüfung beginnt – und der Agent hat längst Vollzug gemeldet.
Agent Hooks greifen früher, mitten in der Produktion. Zarar Siddiqi beschreibt sie als Eingriff in den Workflow des Agenten selbst, nicht als Nachkontrolle danach. Ein Hook bekommt auf stdin ein JSON-Objekt, dessen Form vom jeweiligen Ereignis abhängt. Was darin steht, entscheidet, welche Pfade ein Werkzeug wie jq auslesen muss – und damit auch, ob die Prüfung überhaupt etwas sieht.
Eine Warnung gehört dazu: Nicht jeder Agent bietet dieselben Hooks an. Bei Skills gibt es inzwischen so etwas wie einen gemeinsamen Standard, bei Hooks ist die Lage laut dem Autor eher unübersichtlich. Wer die folgenden Beispiele auf ein anderes Werkzeug übertragen will, muss zuerst nachsehen, welche Ereignisse dort tatsächlich existieren.
PreToolUse: der Riegel vor dem Schreiben
Der erste Hook ist ein PreToolUse-Hook. Er läuft unmittelbar bevor Claude Code ein Werkzeug ausführt, und er ist die eine Stelle, an der sich ein Werkzeugaufruf wirklich verhindern lässt. Der Exit-Code entscheidet. Exit 2 blockiert den Aufruf, und alles, was dabei nach stderr geschrieben wurde, landet als Rückmeldung beim Agenten. Exit 1 protokolliert nur eine Warnung und lässt den Aufruf durch – ein Unterschied, den man einmal verwechselt und danach nie wieder.
Die Prüfung selbst ist unspektakulär. Der Hook liest den Inhalt, den der Agent gerade schreiben will, und sucht darin nach dem rohen <input>-Tag. Findet er ihn, bricht er mit Exit 2 ab und schreibt eine Meldung nach stderr, die den Agenten auf die Komponente <.cinput> verweist. Die Datei landet nie auf der Platte. Der Agent bekommt die Korrektur, bevor der Schaden entsteht.
Ein Write legt den kompletten Dateiinhalt unter .tool_input.content ab, ein Edit dagegen nur den neuen Ausschnitt unter .tool_input.new_string, daneben den alten unter old_string. Der jq-Ausdruck muss daher beide Pfade abdecken, sonst greift die Sperre bei Änderungen an bestehenden Dateien ins Leere. Das ist der halbe Anwendungsfall, keine Feinheit für Perfektionisten.
Zarar Siddiqi nennt ausdrücklich die Alternativen und warum er sie nicht gewählt hat. Ein Satz in der CLAUDE.md wäre die naheliegende Lösung gewesen – und genau die Sorte Anweisung, die gelegentlich ignoriert wird. Eine Linter-Regel wäre besser, greift aber erst, wenn jemand den Linter laufen lässt, und das ist nicht garantiert.
Stop: kein Abschluss bei roter Ratchet-Prüfung
Der zweite Hook ist das Gegenstück. Ein Stop-Hook feuert, wenn der Agent seinen Turn beenden will, und er kann genau das verweigern. Exit 2 bedeutet hier nicht „blockiere ein Werkzeug“, sondern „nein, arbeite weiter“ – die stderr-Meldung erklärt, warum. Zarar Siddiqi lässt an dieser Stelle nur einen Test laufen, den Ratchet-Test unter test/amplify_web/design_system_ratchet_test.exs. Nicht die ganze Suite, weil das langsamer wäre und weil ihn nur diese eine Entscheidung interessiert.
Die Eingabe für diesen Hook ist deutlich dünner, denn es gibt kein Werkzeug zu inspizieren. Es bleibt die Tatsache, dass der Agent aufhören möchte. Der jq-Aufruf greift entsprechend nur auf .stop_hook_active zu. Ist der Test rot, verweigert der Hook den Abschluss, und der Agent arbeitet weiter, bis die Bedingung erfüllt ist.
Zarar Siddiqi ist eigenen Angaben nach selbst in eine Falle getappt. Die Prüfung auf stop_hook_active am Anfang ist nicht optional. Sobald ein Stop-Hook einmal eine Fortsetzung erzwungen hat, kommt das Flag beim nächsten Stop als true zurück. Wer dann nicht aussteigt, baut bei dauerhaft rotem Test eine Endlosschleife aus reparieren, stoppen, blockieren und wieder reparieren, bis jemand die Sitzung killt. Ein Guardrail, der nicht mehr loslässt, ist kein Guardrail mehr.
Die Alternative wäre ein PostToolUse-Hook gewesen, der den Ratchet nach jeder Änderung laufen lässt. Technisch funktioniert das, feuert aber ständig mitten in der Arbeit, wenn der Code legitimerweise halbfertig ist. Der Stop-Hook läuft einmal, an dem Punkt, an dem es zählt. Und er läuft dort deterministisch, nicht mit einer hohen Wahrscheinlichkeit.
Warum Linter, CI und PostToolUse nicht dasselbe leisten
Für beide Fälle gilt dasselbe Muster: Die verfügbaren Alternativen greifen zu spät. Die CLAUDE.md greift theoretisch immer und praktisch manchmal. Der Linter greift, wenn ihn jemand aufruft. Pre-commit und CI greifen beim Commit oder Push, also nachdem der Agent erklärt hat, er sei fertig, und man selbst schon weitergezogen ist. Das ist genau das Problem, das Siddiqi eingangs am pre-commit hook beschreibt.
Der Unterschied liegt nicht in der Sorgfalt, sondern im Zeitpunkt. Ein Prüfschritt, der nach der Arbeit läuft, ist eine Qualitätskontrolle. Ein Prüfschritt, der während der Arbeit läuft und den nächsten Schritt blockiert, ist eine Regel. Dieselbe Prüfung, an zwei Stellen im Ablauf platziert, hat eine völlig andere Wirkung – auch wenn der Code dahinter identisch ist.
Die stille Falle bei jq
Wenn der jq-Pfad nicht stimmt, liefert das Werkzeug null zurück, die Prüfung findet nichts, und der Hook tut stillschweigend gar nichts. Von außen sieht das aus wie ein funktionierender Guardrail. Zarar Siddiqi empfiehlt deshalb, jeden Hook einmal gegen eine echte Payload zu testen, bevor man ihm vertraut – bei .tool_input.content genauso wie bei .stop_hook_active.
Regeln, die wirklich gelten müssen, gehören nicht in einen Absatz mit Großbuchstaben, sondern an eine Stelle im Ablauf, an der sich der Agent nicht vorbeimogeln kann. Ein Block vor der Schreiboperation und eine Sperre am Ende des Turns decken zwei verschiedene Sorten von Fehlern ab – den falschen Code und die voreilige Erfolgsmeldung.
Der Aufwand: zwei Einträge in .claude/settings.json, zwei kleine Skripte, ein Test gegen die echten Eingabedaten. Wer schon einmal eine halbe Stunde damit verbracht hat, rohe Formularfelder aus generiertem Code wieder herauszusuchen, weiß, dass sich das rechnet. Wer weiterhin hofft, dass der Agent den Satz „bitte immer beachten“ diesmal ernst nimmt, braucht die Skripte nicht.
Quelle: zarar.dev
