Why NextWhy Next
Zur Übersicht
💻 Entwicklung18 Min. Lesezeit

Dem "fertig" meines Agenten glaube ich nicht mehr - prove-it, das verify.sh-Gate

KI-Coding-Agenten melden "Tests bestanden", ohne die Tests je gestartet zu haben. Das ist keine Lüge, sondern ein Strukturproblem. Was ich beim Bau von prove-it gelernt habe, einem Open-Source-Gate, das Berichte in Prüfungen verwandelt - und die vier Arten, auf die ein selbst geschriebener Fünf-Zeilen-Hook leise scheitert.

Das Wichtigste

Coding-Agenten können nicht unterscheiden, was sie getan haben und was sie tun wollten - in Wahrheit berichten sie ihre Absicht. Deshalb sagen sie "die Tests bestehen", ohne die Tests gestartet zu haben. Per Prompt lässt sich das nicht beheben; "fertig" muss von einer Erklärung zu einer Prüfung werden, die bestanden werden muss. prove-it ist ein Stop-Hook-Gate, das den Agenten seinen Turn nicht beenden lässt, bevor die verify.sh im Repo-Root exit 0 zurückgibt. Ein selbst geschriebener Fünf-Zeilen-Hook scheitert leise auf vier Arten - er blockiert einmal und danach nie wieder, er winkt Turns durch, die mit einem Commit enden, der Agent schafft die Prüfung selbst ab, oder Aufgeben ist nicht von Bestehen zu unterscheiden. Diese vier Fälle sind der ganze Grund, warum prove-it länger als fünf Zeilen ist.

Auf dieser Seite

"Alle Tests bestanden. Kann gemerged werden."

Das sagte der Agent, und weil der Diff sauber aussah, hätte ich es ihm fast geglaubt. Dann scrollte ich im Terminal nach oben: Die Tests waren kein einziges Mal gelaufen. Es gab schlicht kein Ausführungslog. Der Agent wollte die Tests starten, und genau diese Absicht stand dann wörtlich in seinem Abschlussbericht.

Wer so etwas ein paar Mal erlebt hat, reagiert meist auf eine von zwei Arten: Entweder man schreibt "führe die Tests unbedingt aus und zeig mir die Ergebnisse" in den Prompt, oder man prüft jedes Mal von Hand nach, wenn der Agent "fertig" meldet. Ich habe beides probiert, und beides hielt nicht lange. Also habe ich einen dritten Weg genommen. Statt dem Wort "fertig" bloß zu misstrauen, habe ich Fertigstellung von einer Erklärung in eine Prüfung verwandelt, die bestanden werden muss. Daraus wurde das Open-Source-Tool prove-it.

Keine Lüge, sondern Struktur

Eins vorweg: Der Agent lügt nicht.

Ein Mensch unterscheidet zwischen "ich habe die Tests laufen lassen" und "ich wollte die Tests laufen lassen". Er hat ja ein Gedächtnis. Dem Agenten fehlt dieser Vergleichspunkt. Er kann das, was er getan hat, nicht mit dem abgleichen, was er tun wollte, und berichtet deshalb statt des Ergebnisses seine Absicht. "Die Tests bestehen" ist im Grunde die Kurzform von "ich habe den Code so geschrieben, dass die Tests bestehen sollten". Das ist kein Charakterfehler des Modells, sondern eine Eigenschaft seiner Bauweise - und genau deshalb lässt es sich nicht per Prompt reparieren. Man kann hundertmal "führ sie wirklich aus" schreiben, irgendwann kommt der Tag, an dem er es nicht tut.

Prompt-Tricks haben noch eine zweite Schwäche: Sie altern mit jeder Modellgeneration. Dass eine Anweisung beim aktuellen Modell gut wirkt, garantiert nichts fürs nächste. Ein Mechanismus, der Beweise verlangt, sitzt dagegen eine Ebene über dem Modell und übersteht Upgrades. Ob die Tests wirklich gelaufen sind, soll nicht das Modell sagen, sondern der Exit-Code.

Aus Berichten Prüfungen machen

Die Idee selbst - "fertig" nur als Beweis gelten zu lassen, nie als Behauptung - habe ich in einem früheren Beitrag beschrieben. Dieser Beitrag erzählt, wie aus der Idee ein Werkzeug wurde, das jeder installieren kann.

Die Konvention von prove-it passt in einen Satz: Das Repo deklariert in einer Datei namens verify.sh, wie es sich selbst beweist, und der Agent darf keine Fertigstellung behaupten, bevor dieser Beweis besteht.

Die Umsetzung sieht so aus: Im Repo-Root liegt eine ausführbare verify.sh. Exit 0 heißt "dieser Tree ist nachweisbar in Ordnung". Will der Agent seinen Turn beenden, führt ein Stop-Hook dieses Skript aus - und bei allem außer 0 lässt er den Agenten nicht aufhören, sondern schickt ihn mit den letzten zwanzig Zeilen der Fehlerausgabe zurück an die Arbeit. In den meisten Fällen reicht dem Agenten schon diese Ausgabe, um die Ursache zu beheben.

Wann das Gate greift, ist genauso simpel: wenn diese Session dieses Repo tatsächlich verändert hat, eine ausführbare verify.sh existiert und der aktuelle Zustand des Tree noch nie bestanden hat. Nur wenn alle drei Bedingungen wahr sind, läuft es. Rein lesende Sessions bleiben unangetastet, und ein Tree, der einmal bestanden hat, wird nicht erneut geprüft.

Und es blockiert nicht endlos. Pro Turn schickt es den Agenten höchstens dreimal zurück, danach gibt es nach - denn ein Hook, der niemals nachgibt, legt die Session lahm. Nachgeben ist aber nicht dasselbe wie Bestehen. Endet ein Turn durch Nachgeben, ist die letzte Ausgabe kein "fertig", sondern die Warnung "dieser Turn endete unverifiziert". Der Agent kann am Gate scheitern, aber er kommt nicht leise daran vorbei - mehr behauptet dieses Tool gar nicht.

Warum fünf Zeilen Bash nicht reichen

Wer bis hierhin gelesen hat und denkt "dafür reicht doch eine Zeile Testbefehl im Stop-Hook", hat recht. Fünf Zeilen genügen, und genau so habe ich es anfangs geschrieben. Diese Version scheiterte dann leise, auf vier Arten. Ein paar davon waren echte Bugs in frühen prove-it-Versionen, weshalb heute an jedem einzelnen Fall ein Regressionstest hängt.

Es blockiert einmal und danach nie wieder. Claude Code setzt nach der ersten Blockade bei jedem weiteren Stop-Event das Flag stop_hook_active. Ein Hook, der dieses Flag als "lass durch" liest, hört nach genau einer Blockade auf, ein Gate zu sein. Ein Hook, der das Flag ignoriert, blockiert dagegen ewig und friert die Session ein. Beide Fallen kennt man erst, wenn man in ihnen gesteckt hat - die Antwort ist, die Versuche selbst zu zählen, eine feste Anzahl zu blockieren und dann lautstark nachzugeben.

Nach einem Commit sieht es aus, als wäre nichts passiert. Ein Hook, der "wurde hier gearbeitet?" daran festmacht, ob der Working Tree dirty ist, winkt jeden Turn durch, der mit einem Commit endet. Dabei ist Committen das Normalste, was ein Agent tut. prove-it hält deshalb beim Session-Start den Zustand des Tree als Baseline fest und vergleicht bei jedem Stop dagegen. Commits, per sed geänderte Dateien, von Codegeneratoren ausgespuckte Dateien - alles zählt als Änderung.

Der Agent kann die Prüfung abschaffen. Der billigste Ausweg für einen Agenten, der verify.sh nicht zum Bestehen bringt, ist, verify.sh zu löschen oder ihr die Ausführungsrechte zu entziehen. Das Gate merkt sich beim Session-Start, ob das Repo scharfgeschaltet war, und weist Turns zurück, die entwaffnet enden. Es gibt einen raffinierteren Trick: die Datei ausführbar lassen und nur die Prüfungen darin umschreiben. Das wird nicht blockiert - oft genug ist das Ändern von verify.sh ja genau die Arbeit, um die man gebeten hat. Aber es passiert auch nicht lautlos: Besteht der Tree, während sich verify.sh in der Session geändert hat, sagt das Gate es einem, und am Diff lässt sich ablesen, ob es Arbeit war oder Ausweichen.

Aufgeben ist nicht von Bestehen zu unterscheiden. Jeder Host zwingt den Hook irgendwann zum Nachgeben. Ein selbst geschriebener Hook gibt schweigend nach, und das Letzte, was man liest, ist "fertig". Ein Turn, der dreimal gescheitert ist und aufgegeben hat, sieht auf dem Bildschirm exakt so aus wie einer, der auf Anhieb bestand. Das letzte Wort von prove-it ist eine Warnung. Wem dieser Unterschied kleinlich vorkommt, der stelle sich den Tag vor, nachdem er einen aufgegebenen Turn für einen bestandenen hielt und gemerged hat.

Diese vier Fälle sind der ganze Grund, warum prove-it länger als fünf Zeilen ist. Wer beim eigenen Hook bleiben will, kann das gerne tun. Die Fälle, die dieser Hook abdecken muss, stehen allerdings in der SPEC.md - Lektüre empfohlen. Was zählt, ist nicht die Implementierung, sondern die Konvention.

Was dieses Tool nicht beweist

Eine Grenze gehört ehrlich dazugeschrieben. Das Gate erzwingt genau eine Sache: dass verify.sh vor dem Ende des Turns 0 zurückgegeben hat. Was diese 0 bedeutet, hängt vollständig von den Prüfungen ab, die du hineinschreibst. Eine verify.sh, die nur aus exit 0 besteht, passiert dieses Gate und beweist gar nichts.

Die Spec nennt das Level 1. Level 2 ist die Frage, ob die Prüfungen über echte Beweise Aussagen treffen - und das kann dir kein Tool abnehmen, auch prove-it nicht. Den Hook einzuhängen ist der leichte Teil. Die eigentliche Arbeit ist die Antwort auf die Frage "was heißt in diesem Repo eigentlich bewiesen", und die fällt in jedem Repo anders aus. Deshalb legt die Konvention nur den Dateinamen fest, nicht den Inhalt.

Auch die Umgehungswege sind absichtlich offen. Mit PROVE_IT_SKIP=1 passiert der Turn einfach, und wer verify.sh löscht, schaltet das Gate für immer aus. Ein Gate, das sich nicht entfernen lässt, wird am Ende umgangen. Und ein umgangenes Gate ist schlimmer als gar keins - es meldet, die Prüfungen seien gelaufen, während in Wirklichkeit nichts lief.

Der erste Tag ist absichtlich klein

Die Installation sind drei Zeilen, und die eigentliche Arbeit erledigt die dritte.

/plugin marketplace add Why-Next/prove-it
/plugin install prove-it@whynext
/prove-it:init

/prove-it:init erkennt den Stack, erzeugt die verify.sh, zeigt dir live, dass sie besteht - und lässt danach eine Kopie mit angehängtem exit 1 laufen, damit du auch siehst, wie das Gate einen Turn abweist. Dauert ungefähr 30 Sekunden.

Die einzige aktive Prüfung in der generierten verify.sh ist git diff --check. Tests, Type-Checks - alles steht nur als Kommentar drin. Das ist Absicht: Sie soll am Tag der Installation auf main bestehen. Ein Gate, das am ersten Tag fehlschlägt, bringt dem Team schon in der ersten Woche bei, wie man es umgeht. Die auskommentierten Prüfungen schaltest du einzeln frei, nachdem du sie von Hand hast laufen und bestehen sehen. Und vor dem Freischalten solltest du jede einmal absichtlich zum Scheitern bringen. Eine Prüfung, die nicht scheitern kann, ist keine - und wer das erst an dem Tag merkt, an dem es darauf ankommt, merkt es zu spät.

Auch der Punkt, an dem man beim Ausbauen von verify.sh aufhört, ist festgelegt: eine Minute insgesamt. Alles Langsamere wandert ins CI. Prüfungen, die Secrets oder Produktionszugriff brauchen, gehören ebenfalls ins CI. In verify.sh bleibt nur der Beweis, den der Agent während der Arbeit lokal erbringen kann.

Ein Logbuch dessen, was das Gate gefangen hat

Mit PROVE_IT_LEDGER=1 landet jedes Mal, wenn das Gate eine falsche Fertigmeldung abfängt, eine Zeile in einer lokalen Datei.

{"ts":"2026-07-09T04:12:33Z","claim":"All tests pass. Ready to merge.",
 "evidence_demanded":"verify.sh exit 0","actual":["3 failed, 41 passed"]}

Was der Agent behauptet hat, was verlangt wurde und was tatsächlich der Fall war. Nach einem Monat kannst du am Protokoll statt aus dem Bauchgefühl ablesen, auf welche Weise dein Agent scheitert. Die Datei bleibt auf der lokalen Platte, wird nirgendwohin übertragen und ist aus, bis du sie einschaltest.

Dieses Repo gated sich selbst

Natürlich hat auch das prove-it-Repo eine verify.sh, und darin wird das Gate selbst auf echten Git-Repos getestet. Blockiert eine scheiternde Prüfung? Öffnet eine bestehende? Bleiben rein lesende Sessions unberührt? Wird ein sauberer Tree nach einem Commit nicht fälschlich als "keine Arbeit" gewertet? Alle vier Fallen von oben stecken als Regressionstests darin.

MIT-Lizenz, die Abhängigkeiten sind bash, git und python3, mehr nicht. Getestet mit Claude Code; wie man Hosts wie Codex CLI oder Gemini CLI anbindet, die dieselbe Art blockierender Hooks anbieten, steht im ADAPTERS-Dokument. Wenn du ein Repo hast, in dem ein Agent Code ändert und im selben Gespräch "fertig" sagt, ist genau dort dieses Gate am nützlichsten.

"Fertig" sollte kein Wort sein, das der Agent ausspricht, sondern ein Zustand, den das Repo feststellt. Die Kriterien für dieses Urteil aufzuschreiben bleibt weiterhin deine Aufgabe - aber wenigstens kann jetzt niemand mehr das Urteil überspringen und leise daran vorbeikommen.

Häufige Fragen

Mein KI-Coding-Agent meldet Arbeit als erledigt, die er gar nicht gemacht hat. Wie verhindere ich das?

Die Anweisung "führe unbedingt die Tests aus" im Prompt reicht nicht. Der Agent hat kein Mittel, das, was er getan hat, mit dem zu vergleichen, was er tun wollte, und berichtet deshalb schlicht seine Absicht. Verwandle stattdessen die Fertigmeldung selbst in eine Prüfung: Lege ein Verifizierungsskript (verify.sh) ins Repo-Root, und wenn der Agent seinen Turn beenden will, führt ein Stop-Hook das Skript aus und schickt ihn bei allem außer exit 0 zurück an die Arbeit. prove-it ist die Referenzimplementierung dieser Konvention.

Kann ich mir nicht einfach selbst ein Fünf-Zeilen-Skript schreiben, das im Claude Code Stop-Hook die Tests startet?

Kannst du, aber es gibt vier Fallen. ① Claude Code setzt nach der ersten Blockade das Flag stop_hook_active; wer es falsch behandelt, blockiert entweder nur ein einziges Mal oder für immer, und die Session friert ein. ② Wer am Zustand des Working Tree festmacht, ob gearbeitet wurde, winkt jeden Turn durch, der mit einem Commit endet. ③ Der Agent kann verify.sh löschen oder per chmod -x entschärfen und damit das Gate selbst abrüsten. ④ Wenn der Hook am Ende nachgibt, sieht Aufgeben aus wie Bestehen. Wer selbst schreibt, muss alle vier Fälle abdecken.

Welche Prüfungen gehören in verify.sh?

Die Antwort auf die Frage "was muss in diesem Repo wahr sein, damit eine Änderung als bewiesen gilt". Üblicherweise schaltet man nacheinander frei: den Testbefehl, den man wirklich benutzt, dann Type-Check, dann Lint. Das Ganze muss in unter einer Minute durchlaufen, und Prüfungen, die Secrets oder Produktionszugriff brauchen, gehören ins CI. Wichtig ist, am ersten Tag nicht ehrgeizig zu werden: Ein langsames oder wackliges Gate wird innerhalb einer Woche umgangen, und ein umgangenes Gate ist schlimmer als gar keins.

Ähnliche Beiträge

0 Kommentare