Never Release to Test: Warum wir unsere CI und KI-Agenten nach links verschieben mussten
16’300 CI-Minuten in einer Woche, 18.9 % kompletter Ausschuss und 22 Release-Versuche für eine einzige Anwendung: Warum Releases als Testumgebung missbraucht wurden, wieso Agenten ohne Governance lautlos Standards ignorieren, und wie ein dreistufiger Filter das Problem löst.

In der zweiten Augustwoche 2026 lief unsere GitLab-CI-Infrastruktur heiss: 16’300 Runner-Minuten in sieben Tagen. Davon verbrannten 2’809 Minuten (18.9 %) in Pipelines, die nie einen Mehrwert lieferten, sondern mit vermeidbaren Fehlern abbrachen. Bei einer einzelnen Web-Anwendung zählten wir 22 Release-Tags innerhalb von sechs Tagen, nur um einen lauffähigen Stand auf Produktion zu bekommen.
Die intuitive Diagnose in solchen Momenten lautet fast immer: «Unsere Pipeline ist flaky.»
Die forensische Auswertung aller Pipeline-Läufe dieser Woche zeigte jedoch das exakte Gegenteil: Nur 10.3 % der Abbrüche waren echte Flakes (Netzwerk-Timeouts, Runner-Races). Ganze 78.4 % waren reale Fehler: Syntaxfehler, fehlende Imports, deplatzierte Konfigurationen und kaputte Builds, die Entwickler und autonome KI-Agenten erst nach einem 17-minütigen Remote-CI-Roundtrip bemerkten.
Wir hatten kein Flakiness-Problem. Wir hatten ein Feedback-Latenz-Problem. CI wurde als verlängerter Debugger missbraucht.
Dieser Artikel ist der erste Teil einer zweiteiligen Aufarbeitung. Er beschreibt, wie wir durch drei architektonische Hebel (die Durchsetzung von Agenten-Governance vor Turn 0, die lückenlose Schliessung von Validierungslücken in Merge Requests und ein striktes, evidenzbasiertes Preflight-Gate vor jedem Release) 1’150 Minuten wöchentlichen CI-Müll eliminiert haben.
Die Anatomie des Scheiterns: Was die Daten zeigten
Eine detaillierte Klassifikation aller 46 fehlgeschlagenen Pipelines zwischen dem 8. und 15. August ergab drei strukturelle Brandherde:
| Fehlerkategorie | Anteil Minuten | Symptom & Ursache |
|---|---|---|
| Tag-Kaskaden & Release-Blindheit | 27.3 % (767 min) | Deploy-Jobs schlagen erst auf dem Tag fehl (Docker-Builds, Healthchecks), weil sie im Merge Request gar nicht ausgeführt wurden. |
| Agenten-Regelbrüche & triviale Fehler | 13.7 % (384 min) | Syntaxfehler, ungetestete TypeScript-Typen und fehlende Dependencies in isolierten Worktrees. |
| Frontend- & E2E-Ressourcenstau | 49.0 % (1’377 min) | Parallele Vitest- und Playwright-Läufe verstopfen 32-Kern-Runner ohne CPU-Budgetierung. |
| Tatsächliche CI-Flakes | 10.0 % (281 min) | Vorübergehende Netzwerkfehler und Socket-Timeouts externer Registries. |
Besonders verheerend war die Wechselwirkung zwischen autonomen KI-Agenten und unvollständigen Merge-Request-Pipelines.
Das Phänomen der «lautlosen Abwesenheit» bei KI-Agenten
In unserem Monorepo arbeiten Entwickler Hand in Hand mit autonomen LLM-Agenten (Claude Code, OpenCode, Codex), die in isolierten Git-Worktrees operieren. Sämtliche Squibble-Engineering-Standards (von Commit-Konventionen bis hin zu strikten TDD-Regeln: «Führe niemals einen Test runner direkt aus, nutze mise run <task>») sind zentral in einem gemeinsamen Submodul .agents/ hinterlegt.
Bei der Analyse der Worktrees fiel uns ein gravierender Fehler auf: In 19 von 19 aktiven Agenten-Worktrees war das Submodul .agents/ überhaupt nicht initialisiert.
/worktrees/feat-auth/
├── .agents/ <-- LEER (Submodul nicht ausgecheckt)
├── AGENTS.md -> .agents/rules/AGENTS.md <-- Toter Symlink!
Da Git-Worktrees Submodule standardmässig nicht rekursiv mit auschecken, zeigten die Symlinks für die Agenten ins Leere. Ohne Fehlermeldung starteten die Sprachmodelle in Turn 0 mit Standard-Prompts, ignorierten firmeninterne Konventionen, erstellten ungültige Commit-Formate und committeten ungeprüften Code direkt ins Repository.
Die Lösung: Ein Root-eigener, fail-closed Git-Shim (Layer −1)
Appelle an Agenten oder Entwickler, nach git worktree add manuell git submodule update --init aufzurufen, scheitern in der Praxis. Wir haben das Problem auf Betriebssystemebene gelöst.
Über Ansible haben wir vor dem eigentlichen git-Binary einen Service-spezifischen Wrapper platziert, der jeden Checkout abfängt:
#!/usr/bin/env bash
set -euo pipefail
REAL_GIT="/usr/bin/git"
# Führe das originale Git-Kommando aus
"$REAL_GIT" "$@"
EXIT_CODE=$?
# Nach jedem Checkout oder Worktree-Befehl: Synchronisiere .agents zwingend
if [[ "$EXIT_CODE" -eq 0 && ("$*" =~ "checkout" || "$*" =~ "worktree add") ]]; then
if [[ -f ".gitmodules" ]] && grep -q "\.agents" .gitmodules; then
"$REAL_GIT" submodule update --init --recursive .agents >/dev/null 2>&1 || {
echo "[FATAL] .agents Submodul konnte nicht initialisiert werden. Fail-closed." >&2
exit 1
}
fi
fi
exit $EXIT_CODEDieser Shim arbeitet fail-closed: Kann das Regelwerk nicht synchron und vollständig initialisiert werden, schlägt der gesamte Worktree-Befehl fehl. Kein Agent kann auch nur eine Zeile Code schreiben, ohne dass die monorepo-weiten Guardrails im Kontext geladen sind.
Das Anti-Pattern: «Release to Test»
Der zweite grosse Hebel betraf unsere Release-Pipeline.
Historisch waren viele teure CI-Schritte (das Bauen von Docker-Images, das Hochfahren von Abhängigkeiten über Healthchecks und End-to-End-Delivery-Probes) ausschliesslich an Git-Tags (v*.*.*) gekoppelt. Der Grundgedanke war einst Ressourcenschonung: Man wollte in Feature-Branches keine schweren Container bauen.
Die Folge war fatal: Entwickler und Agenten pushten Feature-Branches, sahen eine grüne Merge-Request-Pipeline (die nur Unit-Tests prüfte), mergten auf main und erstellten sofort einen Release-Tag. Erst auf dem Tag schlug dann der Docker-Build fehl: beispielsweise weil ein COPY-Pfad im Dockerfile nach einem Refactoring nicht mehr stimmte.
Da ein fehlgeschlagener Tag unveränderlich ist, blieb nur ein Ausweg: Fix committen, mergen, neuer Tag (v1.2.1), hoffen.
So entstanden die 22 Release-Tags in sechs Tagen.
Layer 0: Volle Reichweite im Merge Request
Um diesen Teufelskreis zu durchbrechen, haben wir die CI-Matrix radikal umgebaut:
- Downstream-Bridges in MRs: Alle 10 Client-Frontends und Service-Anwendungen triggern bei code-relevanten Änderungen ihre vollständigen Validierungs-Pipelines bereits im Merge Request.
- Immutable Candidate Images: Docker-Images werden bereits im Merge Request gebaut und gegen Registry-Healthchecks getestet. Um Caches nicht zu vergiften, pushen MR-Pipelines unter immutable Tags (
mr-<iid>-<sha>), während der mutable Tag:lateststrikt für erfolgreichemain-Builds reserviert bleibt. - Parallele Smoke-Tests: Auf dem
main-Branch laufen Smoke-Tests ausschliesslich lesend gegen Staging, um gegenseitige Blockaden zu verhindern.
Damit war sichergestellt: Was im MR nicht grün ist, kann main niemals erreichen.
Die letzte Schranke: Das release/preflight-Gate (Layer 1)
Selbst mit grünen MR-Pipelines blieb eine Restunsicherheit: In einem Monorepo mit 17 eigenständigen Deployables (Frontends, APIs, Parser, Hintergrunddienste) können unglückliche Merge-Reihenfolgen auf main subtile Integrationsbrüche erzeugen.
Früher bemerkte man das nach dem Setzen des Release-Tags. Heute verhindert das ein dediziertes Preflight-Skript, das als Pflicht-Job auf main läuft.
# Sammle den Status aller 17 Deployables
DEPLOYABLES=$(nx show projects --with-target=build)
for app in $DEPLOYABLES; do
echo "Prüfe CI-Evidenz für $app..."
# Ermittle, ob die letzte Änderung an diesem Deployable
# eine nachweisbar grüne Pipeline auf main durchlaufen hat
if ! verify_deployable_evidence "$app"; then
echo "[BLOCK] $app hat ungetestete Änderungen oder rote Checks!" >&2
exit 1
fi
done
echo "[READY_TO_TAG] Alle 17 Deployables sind vollständig verifiziert."
exit 0Das release/preflight-Gate prüft rein evidenzbasiert:
- Wurde für jedes der 17 Deployables seit dem letzten Commit ein erfolgreicher Build- und Testnachweis erbracht?
- Gibt es verwaiste Änderungen, die zwar gemergt, aber noch nicht durch die Pipeline gelaufen sind?
Erst wenn dieses Skript mit Exit-Code 0 durchläuft, wird die Schaltfläche oder Automation zum Taggen freigegeben.
Das Resultat
Durch die Implementierung der Stufen Layer −1, Layer 0 und Layer 1 haben wir die Stabilität unserer Pipelines messbar verändert:
- 0 fehlgeschlagene Release-Tags seit Einführung des Preflight-Gates.
- 1’151 Minuten (41 % des wöchentlichen CI-Ausschusses) wurden sofort eliminiert.
- 100 % Konformität bei KI-Agenten: Durch den T3-Git-Shim startet kein Agent mehr ohne initialisierte Regeln und Typechecks.
Im kommenden zweiten Teil dieser Serie widmen wir uns den verbleibenden 1’377 Minuten: Wie wir durch cgroup-basierte Ressourcenisolation in Vitest, schlanke Playwright-Container und automatisierte wöchentliche DX-Scorecards auch den Frontend-Stau beseitigt haben.
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.

