Agentic Engineering

Die Agent-Session ist der CI-Runner – und sie ist schon bezahlt

Agent prüft in seiner Cloud-Session, Pull-Request-CI überspringt: Wie ein Commit-Trailer GitHub-Actions-Minuten spart – und welche Wege scheiterten.


Anthropic schenkt Pro- und Max-Abonnenten seit Ende September bis zu 250 Dollar Guthaben, nur für Claude-Code-Cloud-Sessions. Die Botschaft: Lasst den Agenten in der Cloud arbeiten. Was dabei leicht übersehen wird: Jede Session läuft in einer eigenen VM mit mehreren Kernen, ohne separate Abrechnung.

Der Agent hat also eine vollwertige Build-Maschine, schreibt dort den Code und führt die Tests aus – und schickt trotzdem jeden Push an GitHub Actions, wo dieselben Tests noch einmal laufen, auf Minuten, die man bezahlt. Dieser Artikel zeigt, wie wir die Session zum CI-Runner gemacht haben, an welchen vier Wegen uns der Session-Proxy scheitern ließ, und welcher blieb.

Die Rechnung: Jede Änderung läuft doppelt

Aufgefallen ist es uns, als die inkludierten Action-Minuten aufgebraucht waren – nicht am Monatsende, sondern mittendrin, in einem privaten Repository, an dem hauptsächlich Agenten arbeiten. Wer nachsieht, warum, findet dieselbe Testsuite dreimal am selben Vormittag: einmal in der Session, in der der Agent sie geschrieben hat, einmal beim Push auf den Pull Request, einmal nach dem Merge.

So prüft das übliche Setup jede Änderung mindestens zweimal in der Cloud: bei jedem Push auf den Pull Request, damit man weiß, ob man mergen darf, und noch einmal nach dem Merge auf main, weil dort ein neuer Commit entsteht. Dabei ist nur der Lauf auf main wirklich nötig – er prüft, was ausgeliefert wird. Die PR-Läufe sind Vorabprüfungen. Hat der Agent dieselbe Prüfung schon in seiner Session gemacht, sind sie reine Wiederholung.

Die Cloud-Session ist schon ein CI-Runner

Eine Cloud-Session startet aus einem frischen Clone. Was sie dabei tut, steuert ein SessionStart-Hook im Repo, bei uns .claude/hooks/session-start.sh. Er macht drei Dinge:

  1. Abhängigkeiten und Git-Hooks: uv sync, npm ci, pre-commit install. Damit laufen in der Session dieselben Commit- und Push-Hooks wie lokal.
  2. Die Dienste, die die Tests brauchen: Was in der CI als Service-Container läuft, startet hier im Container der Session – bei uns eine Datenbank, die der Hook anlegt und über eine Umgebungsvariable bekanntmacht. Damit laufen auch die Tests, die mehr als einen Prozess brauchen.
  3. Der Schalter: Er setzt PREPUSH_CI=full. Damit führt der Pre-Push-Hook alles aus, was sonst die PR-CI prüft: die ganze Test-Suite, den Linter über das gesamte Repository, den Format-Check und alle weiteren Validierungen.

Das Ergebnis: Ein git push aus der Session dauert gut zwei Minuten statt ein paar Sekunden – und hat danach alles geprüft, was GitHub Actions prüfen würde. Der Agent wartet die zwei Minuten ab, man muss ihm nur einen längeren Timeout für den Push sagen. Ohne Variable, also auf dem eigenen Laptop, bleibt der schnelle Pre-Push mit den Tests, die der Push berührt.

Die Session-VM kostet nichts extra – Cloud-Sessions zählen laut Anthropic zu den normalen Nutzungslimits des Abos. Die zwei Minuten Prüfung verbrauchen kaum Tokens, der Agent wartet und liest am Ende die Ausgabe des Hooks. Und die Maschine ist stärker als das, was GitHub einem privaten Repository hinstellt:

 

Cloud-Session

GitHub-Runner, privat

CPU / RAM

4 vCPU, 15 GB

2 vCPU, 7 GB

Volle Prüfung, unser Repo

2:07 min

5:48 min

Kosten pro Lauf

im Abo enthalten

6 abgerechnete Minuten, plus Nebenjobs

Session-Werte mit nproc und free in einer laufenden Session gemessen, Runner-Daten aus der GitHub-Doku. Öffentliche Repositories bekommen 4 vCPU und 16 GB kostenlos – wer Open Source macht, hat das Minutenproblem nicht.

Bleibt die eigentliche Frage: Wie erfährt die PR-CI, dass sie sich die Arbeit sparen kann?

Einordnung: CI zurück auf die Maschine ist kein neuer Gedanke

Neu ist der Gedanke nicht. Im April 2024 hat David Heinemeier Hansson die Remote-CI von 37signals abgeschafft und die Prüfung zurück auf die Entwicklerrechner geholt: Die Pipeline für HEY brauchte fünfeinhalb Minuten, sein Linux-Rechner weniger als die Hälfte. Für Codebasen in der Größe von Shopify gelte das nicht, räumt er ein.

Daraus wurden Werkzeuge: Basecamps gh-signoff setzt nach lokalen Tests per gh signoff einen grünen Commit-Status, den ein Ruleset für den Merge verlangt; Rails 8.1 bringt jeder neuen App bin/ci mit optionalem Signoff mit.

Der andere Ansatz spielt die Pipeline lokal nach: act führt GitHub-Actions-Workflows in Docker aus, Dagger schreibt Pipelines als Code, der lokal und in CI gleich läuft. Das spart Push-und-warte-Zyklen, aber keine Minuten – die Cloud-CI läuft danach trotzdem.

Und ja, das widerspricht auf den ersten Blick dem Klassiker. Martin Fowler fordert in Continuous Integration, dass jeder Push auf einer Integrationsmaschine gebaut wird – gerade weil „bei mir läuft’s“ nichts beweist. Genau dieser Einwand wird uns weiter unten wieder begegnen. Wir haben ihn nicht wegdiskutiert, sondern bewusst einen Teil davon in Kauf genommen.

Der Haken: Der Session-Proxy und vier verworfene Nachweise

Die Session muss nach dem Pre-Push einen Nachweis hinterlassen, den ein kleiner Gate-Job in der PR-CI liest. Klingt trivial. Aber eine Cloud-Session ist abgeschottet: Git und GitHub-API laufen über einen Proxy, der die Zugangsdaten außerhalb der VM hält und nur bestimmte Operationen durchlässt. Daran sind drei von vier Ideen gescheitert, die vierte an Git selbst:

  • Git-Note nach refs/notes/checks pushen. Der Git-Proxy nimmt nur Branch-Pushes an und lehnt laut Doku „Pushes von allem außer Branches“ ab. HTTP 403.
  • Commit-Status wie gh-signoff. Der Proxy sperrt Schreibzugriffe auf /statuses mit 403; ein eigener Token als Network-Secret hilft nicht, Secrets werden nie an GitHub-Requests gehängt. Auf einem Laptop mit eigenem gh-Login wäre das die richtige Wahl – in einer Agent-Umgebung nicht.
  • Nachweis-Branch checks/<sha>, den der Gate-Job liest und löscht. Geht vermutlich, aber: Namensregel, Rekursionsschutz, Schreibrechte für die CI, Aufräumen. Zu viele Teile.
  • Trailer per Wrapper, nach der Prüfung per --amend in die Message geschrieben. Ein Pre-Push-Hook kann den Commit nicht mehr ändern, die SHAs stehen. Man bräuchte ein Push-Skript, das alle benutzen; wer es vergisst, fällt still auf die volle CI zurück.

Zwei Dinge haben uns überrascht. Die 403 auf die Status-API kommt nicht von GitHub – die Antwort hatte weder Server: github.com noch X-GitHub-Request-Id, der Proxy hat sie selbst erzeugt; ein lesender Aufruf derselben API lieferte 200. Und diese Sperre steht nirgends, dokumentiert sind nur GraphQL und fremde Repositories. Wer sich wundert, warum sein Token nichts bewirkt: erst auf die Response-Header schauen.

Die Lösung: Der Trailer ist eine Forderung, kein Beleg

Die entscheidende Wendung kam aus einer simplen Rückfrage: Warum schreiben wir den Trailer nicht vorher? Wir gehen davon aus, dass die Prüfung durchläuft. Schlägt sie fehl, findet kein Push statt, und wir reparieren.

Damit ändert sich die Bedeutung. Prepush-CI: full heißt nicht „diese Prüfung ist bestanden“, sondern „dieser Commit darf den Rechner nur nach der vollen Prüfung verlassen“. Der Hook setzt das durch. Kommt ein Commit mit Trailer auf GitHub an, muss die Prüfung bestanden sein, sonst wäre er nicht da.

  1. Beim Commit: Ist PREPUSH_CI gesetzt, schreibt ein commit-msg-Hook mit git interpret-trailers den Trailer in die Message. --where start setzt ihn vor Co-Authored-By, --if-exists doNothing macht ihn idempotent.
  2. Beim Push: Trägt der gepushte Commit den Trailer, läuft der volle Modus – egal, wie die Variable steht. Sonst könnte jemand in der Cloud committen und später lokal ohne Variable pushen. pre-commit reicht den gepushten SHA als PRE_COMMIT_TO_REF an den Hook durch.
  3. Ehrlichkeitsprüfung: Ein Pre-Push-Hook prüft den Arbeitsbaum, nicht den Commit. Deshalb bricht er ab, wenn der gepushte Commit nicht der ausgecheckte HEAD ist oder der Baum schmutzig ist.
  4. In der PR-CI: Ein changes-Job liest git log -1 --format='%(trailers:key=Prepush-CI,valueonly)' vom Head-SHA. Steht dort full, werden alle anderen Jobs übersprungen. Bei Pushes auf main wird der Trailer ignoriert, dort läuft immer alles.

Das Ergebnis: keine zusätzlichen Refs, keine API-Schreibrechte, kein Wrapper, kein Aufräumen. Ein Rebase auf main ist automatisch abgedeckt: Die neuen Commits behalten den Trailer, also läuft beim nächsten Push wieder die volle Prüfung auf dem neuen Stand. Im ersten echten Pull Request meldete der Gate-Job VERIFIED: true, beide Workflows übersprangen alles außer dem changes-Job.

Natürlich kann jeder den Trailer von Hand schreiben. Das gilt für Note, Status und Branch genauso, und auch gh-signoff vertraut am Ende dem, der unterschreibt. Lokale CI ist ein Vertrauensmodell, kein Sicherheitsmodell.

Was wir bewusst offen lassen

Hier kommt Fowlers Einwand zurück. Der Pre-Push prüft den Branch; die PR-CI hätte den Probe-Merge mit aktuellem main geprüft. Wurde dort inzwischen eine Funktion umbenannt, die der Branch noch aufruft, sind beide Hälften grün und zusammen rot. In der Praxis ist die Lücke klein: Wer vor dem Merge auf main rebased, löst wegen des Trailers die volle Prüfung auf genau dem Stand aus, der gemergt wird. Nur wer GitHub rebasen lässt, umgeht die Hooks – dann wird main rot, der Lauf dort blockiert das Deployment, jemand bessert nach. Produktion bleibt heil. Schließen ließe sich die Lücke, indem das Gate nur bei aktuellem Branch überspringt, aber das kostet wieder volle Läufe für jeden veralteten. Wir zählen stattdessen, wie oft main deswegen rot wird.

Das Tool gibt es jetzt: prepush-ci

Wir haben die Idee durch eine Software Factory laufen lassen. Herausgekommen ist prepush-ci, gebaut von fünf Claude-Code-Loops auf Spool – dem Hub für dauerhaft laufende Claude-Code-Agenten, den unser Kollege Enes Alataş entwickelt –, seit heute auf PyPI: uv tool install prepush-ci, oder uvx prepush-ci skill und den Agenten das Repo einrichten lassen – er fragt nach dem Prüfbefehl und öffnet einen Pull Request.

Dabei ist mehr entstanden als im Entwurf oben: Hook-Manager egal (native, pre-commit, husky, lefthook), Gate für GitHub Actions und GitLab CI, PREPUSH_CI=off plus prepush-ci strip als dokumentierter Weg, wenn ein Push ausnahmsweise ohne Prüfung gehen muss, doctor für kaputte Einrichtungen. Variable PREPUSH_CI=full, Trailer Prepush-CI: full. Version 0.1.0, Pre-Alpha. Agenten haben ein Werkzeug gebaut, damit Agenten billiger pushen.

Nicht nur Claude: Jede Agent-VM ist ein Runner

Claude-Cloud-Sessions waren der Anlass, nicht der Punkt. Cloudroom gibt jedem Agenten eine eigene Sandbox mit Admin-Rechten, 100 Cloud-Agenten im Monat kostenlos; HumanLayer lässt Sessions auf Cloud-Daemons weiterlaufen, wenn der Laptop zu ist; Spool steckt jeden seiner Loops in einen eigenen Container. Überall dort läuft die volle Prüfung, bevor der Push abgeht. PREPUSH_CI=full in der Sandbox ist alles, was fehlt, damit die PR-CI das weiß.

Je mehr Pushes von Agenten kommen, desto schiefer wird die alte Rechnung. Fowlers Integrationsmaschine bleibt – für main, wo der zusammengeführte Stand geprüft wird. Auf dem Pull Request lief sie nie aus diesem Grund mit, sondern weil der gehostete Runner die einzige Maschine war, die jeder hatte. Die hat der Agent jetzt selbst.

Zusammenfassung

  1. Die Maschine ist schon da. Eine Cloud-Session des Agenten läuft in einer eigenen VM mit vier Kernen, ohne separate Abrechnung. Dieselbe Prüfung dauert dort 2:07 statt 5:48 Minuten und kostet keine Action-Minuten.
  2. Der Trailer ist eine Forderung, kein Beleg. Der Hook schreibt Prepush-CI: full schon beim Commit und setzt die volle Prüfung beim Push durch. Kommt ein Commit mit Trailer an, muss sie bestanden sein – sonst wäre er nicht da.
  3. Nur der Pull Request spart. Das Gate überspringt dort die Jobs, die schon gelaufen sind. Auf main läuft weiterhin alles, denn das ist die Integrationsmaschine, die zählt.
  4. Die Lücke bleibt offen. Der Pre-Push prüft den Branch, nicht den Probe-Merge mit aktuellem main. Wer vorher rebased, löst die volle Prüfung erneut aus; wer es GitHub überlässt, riskiert ein rotes main – und merkt es vor dem Deployment.
  5. Was man braucht. Einen Prüfbefehl, zwei Hooks und eine Zeile im Workflow. Lokale CI ist ein Vertrauensmodell, kein Sicherheitsmodell – wer den Trailer von Hand schreibt, kann das tun.

Similar posts