Zum Inhalt springen

Engineering · · 4 Min. Lesezeit

Exit 0, und nichts geschrieben

Codex meldete im Write-Mode Erfolg, obwohl keine Datei entstand. Der Fehler lag in der Linux-Sandbox, doch die dauerhafte Lehre betrifft unser Gate.

Ein grüner Exit-Code über einem leeren Arbeitsbaum: Erst die Prüfung der Datei macht den fehlgeschlagenen Write-Mode sichtbar.

Ein Worker bekam einen klaren Auftrag: Lege eine Datei mit einer Zeile Inhalt an. Codex lief im Write-Mode, meldete Exit-Code 0 und erklärte anschliessend, dass der Patch fehlgeschlagen sei. Die Datei war nicht da.

Ein Absturz wäre besser gewesen. Ein Exit-Code ungleich null hätte den Orchestrator gestoppt und die Arbeit sichtbar offen gelassen. Exit 0 behauptete dagegen, der Worker sei fertig. Wer nur dem Prozess und seinem Bericht glaubte, konnte eine Delegation abhaken, bei der sich im Repository nichts geändert hatte.

Das war kein theoretischer Randfall. Unsere Delegations-Policy schickt klar beschriebene Implementierung an gpt-5.6. Während der Fehler bestand, landete jedes solche Paket entweder bei einem teureren Modell oder in einem stillen Fehlversuch. Das Werkzeug, das Arbeit prüfen sollte, brauchte selbst ein Gate.

Eine Probe am richtigen Ort

Der kleinste brauchbare Test bestand aus einer Datei. Wir starteten codex exec im Root des vertrauten Repositorys und verlangten .codex-probe.txt mit genau einer Zeile. Danach prüften wir Prozessstatus und Dateisystem getrennt.

Probe im vertrauten Repository

codex exec "Create .codex-probe.txt containing exactly PROBE_OK"

exit code: 0
.codex-probe.txt: NOT WRITTEN

The patch writer was denied unexpectedly...
patch: failed
Unable to create `.codex-probe.txt`: the workspace write sandbox failed.

Die erste Probe war schlechter. Sie lief unter /tmp und endete mit «Not inside a trusted directory». Das war zwar ein Fehler, aber ein anderer. Erst im Repository erreichte der Auftrag den Patch Writer und reproduzierte den stillen Write-Mode. Beim Debuggen entscheidet der Ort mit darüber, welchen Fehler man misst.

Read-only war nie betroffen. Dieser Modus muss keine schreibbare Sandbox aufbauen und konnte weiterhin Repositorys untersuchen, Pläne erstellen und Reviews liefern. Nur der Pfad, der tatsächlich Dateien ändern sollte, fiel aus.

Zwei Defekte hinter demselben Symptom

Am 14. August 2026 wurde das Verhalten erstmals festgehalten. Eine zweite Messung am 1. September ergab wieder REAL_EXIT=0 und FILE=ABSENT. Am 2. September lief dieselbe Probe nach dem Fix durch und die Datei enthielt PROBE_OK. In dieser Host-Konfiguration hatte der Write-Mode dazwischen nie funktioniert.

Unsere erste Erklärung war falsch. Wir vermuteten, bubblewrap lasse sich nicht in der Sandbox einer laufenden Agenten-Session verschachteln. Eine Wiederholung aus einer gewöhnlichen SSH-Shell, ohne Agenten-Sandbox im Prozessbaum, erzeugte jedoch denselben Fehler. Verschachtelung war nicht die Ursache.

Codex ab Version 0.14x verwendet unter Linux bubblewrap, kurz bwrap, um den Workspace-Write-Prozess in einer Sandbox auszuführen. Auf dem Host war das Distributions-Paket nicht installiert. Codex wich deshalb auf das mit seinem npm-Paket ausgelieferte Binary aus.

Ubuntu 24.04 beschränkt unprivilegierte User-Namespaces über AppArmor. Ein bwrap ohne passendes Profil durfte seinen Namespace nicht anlegen und scheiterte beim Schreiben der UID-Zuordnung mit Permission denied. Ohne Namespace gab es keine Sandbox. Ohne Sandbox lehnte der Patch Writer jede Änderung ab.

Der zweite Defekt lag eine Ebene höher: Codex gab diesen Fehlschlag trotzdem als Exit 0 an den Aufrufer zurück. Die Sandbox liess sich auf unserem Host reparieren. Die Bedeutung dieses Exit-Codes konnten wir dort nicht reparieren.

Der Fix brauchte zwei Anläufe

Der erste Fix installierte bubblewrap aus der Ubuntu-Distribution, bevor die Codex CLI eingerichtet wird. Das war sinnvoll, aber nicht ausreichend. Wir hatten erwartet, dass das Paket /etc/apparmor.d/bwrap mitbringt. Die erste echte Ansible-Ausführung prüfte genau diese Annahme und fiel rot aus.

dpkg -L bubblewrap zeigte für Paketversion 0.9.0-1ubuntu0.1 das Binary und eine sysctl-Datei, aber kein AppArmor-Profil. Die sysctl-Datei setzt kernel.unprivileged_userns_clone=1, einen Debian-Schalter, den Ubuntu 24.04 für diese Entscheidung nicht verwendet. Das Paket allein änderte deshalb am Symptom nichts.

Der zweite Commit lieferte das Profil über unsere Ansible-Rolle selbst aus und lud es mit apparmor_parser --replace. Es folgt der Form, die Ubuntu für vergleichbare Programme verwendet: Das benannte Binary erhält genau die Erlaubnis userns,. Die Rolle prüft anschliessend sowohl die Datei als auch das im Kernel geladene Profil. Eine Datei auf der Platte gewährt noch nichts.

Nach diesem Apply schrieb die Probe PROBE_OK. Der Write-Mode war wieder nutzbar. Der falsche Exit-Code blieb ein bekanntes Verhalten für den Fall, dass der Patch Writer erneut abgewiesen wird.

Das rote Gate prüft den Baum

Unsere interne Regel für Verifikation verlangt mehr als einen grünen Lauf. Ein Gate ist erst belegt, wenn eine absichtlich kaputte Eingabe es rot macht. Für einen schreibenden Worker bedeutet das: Der erwartete Effekt muss im Arbeitsbaum sichtbar sein.

Die Gegenprobe verweigerte Codex den Schreibzugriff absichtlich mit -s read-only. Codex lieferte weiterhin Exit 0, die Datei blieb erwartungsgemäss aus, und unser nachgeschaltetes Gate wurde rot. Damit war der stille Pfad geschlossen, obwohl das fremde Programm selbst noch den falschen Status meldete.

Diese Baumprüfung steht heute in unserer Delegations-Policy. Sie bleibt auch dann sinnvoll, wenn eine spätere Codex-Version den Exit-Code korrigiert. Ein Prozessstatus beweist, was ein Prozess über sich meldet. Ein Diff beweist, ob die beauftragte Änderung existiert.

Was der Plan falsch hatte

Fast zwei Wochen lang behandelten wir jedes betroffene Delegations-Paket einzeln. Implementierung wurde auf ein anderes Modell umgeroutet, Read-only blieb bei Codex. Der Workaround war billig genug, um die Ursachenanalyse immer wieder zu verdrängen.

Auch die erste technische Diagnose passte zu gut zur sichtbaren Architektur. Eine Sandbox lief scheinbar in einer Sandbox, also musste Verschachtelung schuld sein. Erst die Probe aus der SSH-Shell widerlegte diese Geschichte. Danach lag der fehlende Paketbestand offen vor uns. Beim ersten Fix folgte gleich die nächste falsche Annahme: Das Distributions-Paket werde wohl das nötige Profil enthalten.

Beide Irrtümer überlebten nur bis zur nächsten Messung. Das ist der freundlichste Teil dieser Geschichte. Der weniger freundliche Teil ist, dass wir diese Messungen früher hätten machen können.

Fazit

Der konkrete Fehler gehört zu diesem Linux-Host. Auf einem Rechner ohne AppArmor-Beschränkung oder mit bereits passendem bwrap-Profil wäre er so nie aufgetreten. Auch die vermutete Verschachtelung spielte nachweislich keine Rolle.

Die Betriebslehre ist breiter. Coding-Agenten sind Worker in einer Pipeline, und ihre Selbstauskunft ist kein Beleg. Wer sie schreiben lässt, muss den geschriebenen Zustand prüfen. Bei uns ist das ein nicht leerer Arbeitsbaum. In einem anderen System kann es ein Artefakt, ein Datensatz oder ein reproduzierbarer Test sein.

Stille ist kein Erfolgssignal.

Patrick Lehmann

Architecture & Governance Lead

Squibble GmbH

Bringt seit über zwanzig Jahren Struktur in gewachsene IT-Landschaften – als Architekt, Entwickler und Betreiber. Schreibt hier über die Systeme, die bei Squibble tatsächlich laufen, und über die Entscheide dahinter.