# AI-Agents öffnen Pull Requests mit sicheren Kontrollen

AI-Agents, die über eine API Pull Requests öffnen, können echte Entwicklungszeit sparen. Das funktioniert aber nur, wenn das Repository jede generierte Änderung als nicht vertrauenswürdigen Beitrag mit nachvollziehbarem Urheber behandelt. Die sinnvolle Grenze ist einfach: Ein Agent darf eine Änderung vorbereiten. Menschen und Repository-Kontrollen entscheiden, ob sie in die Codebasis gehört.

Ich habe Teams erlebt, die diesen Prozess unsicher machten, weil sie sich auf die Codequalität des Modells konzentrierten und die Berechtigungen außen herum ignorierten. Die schlimmsten Fehler sind meist banal. Eine Aufgabe für einen Staging-Service landet in einem Produktions-Repository. Ein weitreichendes Token gewährt unbemerkt Schreibzugriff auf jedes Projekt. Nach einem Timeout öffnet ein Agent zehn nahezu identische Pull Requests. Jemand merged einen davon, weil der Titel plausibel klingt.

Ein sicherer Aufbau setzt nicht voraus, dass der Agent vorsichtig ist. Er begrenzt, wo der Agent handeln darf, zwingt die Änderung durch den normalen Review-Prozess und hinterlässt einen Datensatz, der später eine Untersuchung ermöglicht.

## Der Repository-Umfang muss ausdrücklich festgelegt und technisch erzwungen werden

Ein Agent sollte Berechtigungen für eine benannte Gruppe von Repositorys erhalten, nicht einen Wildcard-Umfang für die gesamte Organisation, den jemand später einschränken will. Der Repository-Umfang beantwortet eine konkrete Frage: Welche Codebasen darf dieser Prozess lesen, in denen darf er Branches schreiben und gegen welche darf er Pull Requests öffnen?

Halte die Allowlist außerhalb des Instruktionstexts des Agents. Prompts können Verhalten steuern, aber keine Autorisierung erzwingen. Die Komponente, die das Repository-Zugangstoken hält oder den API-Aufruf ausführt, muss ein Repository ablehnen, das nicht auf der Allowlist steht.

Lege für jedes erlaubte Repository die zulässigen Basis-Branches und den erlaubten Schreib-Namensraum fest. Ein sinnvoller Eintrag sieht so aus:

```yaml
repositories:
  - name: acme/payments-api
    base_branches: ["main", "release/2025.1"]
    write_branch_prefix: "agent/"
    pull_request_drafts: true
  - name: acme/docs
    base_branches: ["main"]
    write_branch_prefix: "agent/"
    pull_request_drafts: false
```

Dieses Fragment verhindert einen häufigen Fehler: Ein Agent erhält die Anfrage, den Text im Checkout zu korrigieren, findet eine ähnliche Datei in einem Repository, das er durchsuchen kann, und schreibt dort, weil sein Zugangstoken es erlaubt. Die Allowlist macht daraus eine abgelehnte Anfrage statt einer Aufräumarbeit für jemand anderen.

Zum Umfang gehören auch die Repository-Operationen. Die meisten Agents müssen Dateien lesen, einen Branch erstellen, Commits pushen, Checks lesen sowie einen Pull Request erstellen oder aktualisieren. Repository-Einstellungen ändern, Webhooks registrieren, geschützte Branch-Regeln anpassen, Deploy Keys hinzufügen, Mitglieder verwalten oder Änderungen mergen müssen sie nur selten. Gewähre diese Berechtigungen nicht, nur weil sie in einem bequemen, weitreichenden Token enthalten sind.

Verwende eine eigene Bot-Identität statt des persönlichen Zugriffstokens eines Entwicklers. Bei einem persönlichen Token bleibt die Zuordnung unklar, Änderungen an der Aufgabe werden schlecht abgebildet und oft besitzt es Berechtigungen, an deren Vergabe sich niemand mehr erinnert. Eine Bot-Identität gibt dir einen einzelnen Akteur, den du bei Fehlverhalten sperren kannst.

Auch der Lesezugriff verdient dieselbe Aufmerksamkeit wie der Schreibzugriff. Ein Agent, der jedes private Repository untersuchen kann, könnte Quellcode oder Konfiguration über seine Logs, den Aufgabenkontext oder seine Antworten offenlegen. Gib ihm die kleinste sinnvolle Repository-Gruppe, selbst wenn er kein direktes Schreibzugangstoken erhält.

## Ein Branch-Präfix ist eine Ausführungsgrenze, keine Namenskonvention

Der Agent sollte Branches nur unter einem eigenen Präfix wie `agent/` erstellen dürfen. Der Repository-Server sollte diese Einschränkung erzwingen. Eine Konvention im Prompt wird irgendwann verletzt, etwa durch einen fehlerhaften Tool-Aufruf, einen Retry-Bug oder einen Agent, der eine zu weit gefasste Aufgabe erfüllen will.

Schütze `main`, Release-Branches, Umgebungs-Branches und alle Branches, die automatisch Deployments auslösen. Der Agent darf nicht in sie pushen. Er darf sie nicht per Force-Push überschreiben und die Regeln, die sie schützen, nicht ändern.

Branchnamen sollten so deterministisch sein, dass sie sich untersuchen lassen, und zugleich Kollisionen vermeiden. Nimm eine Aufgabenreferenz sowie ein kurzes zufälliges Suffix oder eine Ausführungs-ID auf:

```text
agent/OPS-1842-retry-payment-7f3a
```

Lass den Agent Aufgabentitel nicht direkt als Branchnamen verwenden. Titel können Geheimnisse, Kundennamen, unsichere Zeichen oder irreführende Formulierungen enthalten. Erzeuge den Branchnamen im Controller und übergib ihn dem Agent als unveränderlichen Wert.

Auch für den Basis-Commit braucht es eine ausdrückliche Regel. Wenn der Controller einen Lauf startet, sollte er den freigegebenen Basis-Branch auf eine Commit-SHA auflösen und diese speichern. Der Agent erstellt seinen Branch von dieser SHA, nicht von dem, was `main` nach einem langen Lauf zur Codegenerierung gerade bedeutet. Das beseitigt Drift nicht, macht sie aber sichtbar und reproduzierbar.

Ein Branch darf nur Commits enthalten, die zu seiner zugewiesenen Aufgabe gehören. Das bedeutet: kein nebenbei ausgeführter Formatierungsdurchlauf, kein unabhängiges Dependency-Upgrade und kein Versuch, angrenzenden Code aufzuräumen, nur weil er seltsam aussieht. Generierte Änderungen wirken oft überzeugend. Reviewer übersehen unabhängige Änderungen, wenn sie in einem ansonsten plausiblen Patch stecken.

Lege vor Beginn der Arbeit ein Änderungsbudget fest. Es kann die Zahl geänderter Dateien, die Gesamtzahl der Zeilen oder Pfade außerhalb der angeforderten Komponente begrenzen. Das Budget ist für sich genommen keine Risikobewertung. Es ist ein Warnsignal, das dem Agent sagt, er soll stoppen und eine neue Aufgabe anfordern, statt eine kleine Reparatur still in eine Änderung des gesamten Repositorys zu verwandeln.

## Das Erstellen eines Pull Requests braucht eine geprüfte API-Transaktion

Eine erfolgreiche HTTP-Antwort beweist nicht, dass der Agent den richtigen Pull Request geöffnet hat. Der Controller muss Repository, Head-Branch, Basis-Branch, Commit-SHA und die zurückgegebene Pull-Request-Kennung prüfen, bevor er Erfolg meldet.

Bei einer API im GitHub-Stil sind die wichtigen Anfragefelder der vorgeschlagene Titel, `head`, `base`, der Inhalt und der Draft-Status. Der genaue Endpunkt variiert je nach Forge, die Sicherheitsprüfungen bleiben jedoch gleich:

```json
{
  "title": "OPS-1842: retry transient payment gateway failures",
  "head": "agent/OPS-1842-retry-payment-7f3a",
  "base": "main",
  "body": "Task: OPS-1842\nBase commit: 4b2c...\nTests: unit payment retry suite\nLimits: no configuration changes",
  "draft": true
}
```

Frage vor dem Senden dieser Anfrage den Branch ab und bestätige, dass seine aktuelle SHA dem für den Lauf gespeicherten Commit entspricht. Hole den Pull Request nach der Antwort erneut ab und vergleiche `head`, `base` und Status mit der Anfrage. Speichere die unveränderliche Pull-Request-Nummer oder Node-ID der Plattform, nicht nur die URL.

Retries brauchen eine besondere Behandlung. Netzwerk-Timeouts führen zum klassischen Problem doppelter Pull Requests: Der Server kann Pull Request 418 bereits erstellt haben, während der Client die Antwort nicht erhalten hat und es erneut versucht. Halte im Controller einen Idempotenzdatensatz mit Aufgaben-ID, Repository, Branch, Basis-SHA und Pull-Request-Nummer. Suche bei einem Retry nach dem bestehenden Branch und Pull Request, bevor du einen Erstellungsaufruf sendest.

Verwende einen Aufgabentitel nicht als einzigen Idempotenzwert. Eine wiederkehrende Anfrage wie «generierte Dokumentation aktualisieren» würde mit einem früheren Lauf kollidieren. Die Ausführungs-ID sollte einen einzelnen Lauf identifizieren, während die Aufgaben-ID Menschen hilft, zusammengehörige Arbeiten zu erkennen.

Draft-Pull-Requests sind ein guter Standard für Agent-Arbeit. Sie zeigen Reviewern, dass die Änderung existiert, aber das vom Ersteller erklärte Abschlusskriterium noch nicht erfüllt ist. Ein Agent darf einen Pull Request erst auf «ready» setzen, wenn die erforderlichen Befehle abgeschlossen sind und der Laufdatensatz ihr Ergebnis enthält. Wenn dein Repository keine Drafts verwendet, bringe ein Label wie `agent-created` über einen vertrauenswürdigen Controller an, nicht über Text, den der Agent selbst verfasst hat.

## Reviewer-Zuweisungen müssen Ownership und Risiko folgen

Der erste Reviewer sollte aus den Ownership-Regeln des Repositorys stammen, nicht aus einer Vermutung des Agents, wer sich auskennen könnte. Die CODEOWNERS-Dokumentation von GitHub beschreibt eine Zuordnung von Dateien zu Verantwortlichen, die Reviews für geänderte Pfade anfordern kann. GitLab bietet vergleichbare Freigabe- und Code-Owner-Mechanismen. Diese Dateien eignen sich als Routing-Daten, machen ein Review aber nicht automatisch verpflichtend, solange Branch-Protection- oder Merge-Regeln es nicht verlangen.

Dieser Unterschied ist wichtig. Ein Repository kann einen angeforderten Code Owner anzeigen und je nach Einstellungen trotzdem einen Merge ohne dessen Zustimmung zulassen. Behandle Routing und Durchsetzung als getrennte Kontrollen. Prüfe beide mit einem absichtlich nicht autorisierten Test-Pull-Request, bevor du der Richtlinie vertraust.

Verwende die Liste der geänderten Dateien nach dem letzten Commit, nicht die Pfade, die der Agent ändern wollte. Ein generierter Patch kann spät im Lauf noch eine gemeinsam genutzte Bibliothek, ein Deployment-Verzeichnis oder einen Migrationsordner berühren. Die Reviewer-Berechnung muss sehen, was tatsächlich geändert wurde.

Füge einen verantwortlichen Menschen hinzu, wenn die Aufgabe Bereiche betrifft, in denen Ownership-Regeln zu weit gefasst sind oder fehlen. Diese Person trägt die Verantwortung für die Absicht der Aufgabe. Ein Code Owner kann bestätigen, dass die Implementierung zu einer Komponente passt. Die verantwortliche Person kann bestätigen, dass das gewünschte Verhalten für das Produkt richtig ist. Weise nicht allein deshalb ein Dutzend Personen zu, weil das Diff mehrere Grenzen überschritten hat. Große Reviewer-Listen führen oft dazu, dass jeder annimmt, jemand anderes habe den schwierigen Teil geprüft.

Einige Pfade sollten einen strengeren Ablauf erzwingen. Beispiele sind Datenbankmigrationen, Autorisierungscode, Build- und Release-Definitionen, Dependency-Manifestdateien, Infrastrukturkonfiguration und generierte Artefakte. Die richtige Reaktion lautet nicht immer «Agent blockieren». Oft heißt sie «den Owner verlangen, der die Folgen versteht». Eine Migration kann Unit-Tests bestehen und trotzdem ein Rollback unmöglich machen.

Weise Reviewer erst per API zu, wenn der Pull Request existiert, und prüfe anschließend die resultierende Zuweisung. Wenn eine Owner-Gruppe keine Anfragen empfangen kann, sollte der Controller den Pull Request als blockiert markieren, statt still einen beliebigen Entwickler einzusetzen. Ein stiller Fallback macht eine Ownership-Regel zur Dekoration.

## Tests liefern Belege, die Freigabe entscheidet über die Annahme

Ein Agent sollte genau angeben, was er ausgeführt hat, was nicht und warum. «Tests bestanden» ist ohne Befehle, Exit-Status und die getestete Commit-SHA eine wertlose Aussage. Speichere diese Belege zusätzlich außerhalb des Pull-Request-Texts, weil ein Agent den Text später ändern kann.

Verwende für jeden Lauf einen kleinen strukturierten Bericht:

```json
{
  "run_id": "run_01J...",
  "repository": "acme/payments-api",
  "head_sha": "8c71...",
  "commands": [
    {"command": "npm test -- payment-retry", "exit_code": 0},
    {"command": "npm run lint", "exit_code": 0}
  ],
  "not_run": ["integration suite requires payment sandbox approval"]
}
```

Der Controller sollte den Wechsel zu «ready for review» ablehnen, wenn der Nachweis eine andere SHA als die Spitze des Branches nennt. Damit wird eine subtile, aber häufige Abfolge erkannt: Der Agent führt Tests aus, macht danach noch eine «kleine» Korrektur und öffnet den Pull Request, ohne etwas erneut zu testen.

Erforderliche Status-Checks sollten auf Repository-Seite bleiben. Der Agent darf keine Checks umgehen, den eigenen Pull Request freigeben, Branch-Protection ändern oder mergen können. Ein Check kann bestätigen, dass ein bekannter Befehl erfolgreich war. Er kann nicht bestätigen, dass eine neue Autorisierungsregel korrekt ist, die Anforderung verstanden wurde oder die Aufgabe in diesem Repository überhaupt hätte begonnen werden sollen.

Lass eine generierte Zusammenfassung nicht an die Stelle einer Diff-Prüfung treten. Gute Zusammenfassungen helfen Reviewern bei der Orientierung, sind aber Aussagen des Urhebers. Reviewer brauchen die tatsächlichen Dateiänderungen, Tests, den relevanten Issue-Kontext und alle bewusst ausgelassenen Punkte.

## Jede erstellte Änderung braucht einen Audit-Datensatz, der einen Vorfall übersteht

Eine Pull-Request-URL ist kein Audit-Datensatz. Sie verschwindet, wenn Repositorys verschoben, Branches gelöscht, Zugriffe geändert oder Beschreibungen bearbeitet werden. Führe einen unveränderlichen Ereignisdatensatz, mit dem ein Ermittler feststellen kann, wer einen Lauf gestartet hat, welcher Prozess welchen Aufruf ausgeführt hat, welches Repository geändert wurde und was das System zurückgegeben hat.

Speichere mindestens diese Felder:

- eine Lauf-ID und die ursprüngliche Aufgaben- oder Ticketreferenz
- die Identität des ausführenden Bots und des authentifizierten Agent-Prozesses
- Repository, Basis-Branch, Basis-SHA, Head-Branch und jede erstellte Commit-SHA
- API-Anfragetyp, unveränderliche Pull-Request-Kennung, Zeitstempel und Ergebnisstatus
- Reviewer-Anfragen, Freigaben, Check-Ergebnisse sowie Ereignisse zu Schließung, Merge oder Ablehnung

Lege standardmäßig keine Klartext-Zugangsdaten, vollständigen Quelldateien oder beliebigen Aufgaben-Prompts in das Audit-Log. Ermittler brauchen zuverlässige Fakten zu Aktionen, nicht noch eine unkontrollierte Kopie sensibler Inhalte. Wenn du Patch-Inhalte aufbewahrst, speichere einen Inhaltsdigest und wende deine normalen Aufbewahrungs- und Zugriffsregeln an.

Die Unterscheidung zwischen Aktivitäts- und Entscheidungslog sollte erhalten bleiben. Ein Aktivitätslog sagt, dass ein API-Aufruf Pull Request 418 erstellt hat. Ein Entscheidungslog sagt, wer diese Agent-Sitzung freigegeben, wer ihre Autorisierung geändert und wer sie widerrufen hat. Während eines Vorfalls sind beide wichtig. Du musst wissen, was passiert ist und warum der Akteur zu diesem Zeitpunkt berechtigt war.

Sallyport kann Agent-Sitzungen sowie einzelne HTTP- oder SSH-Aufrufe in Journalen festhalten, die aus seinem verschlüsselten, hashverketteten Audit-Log abgeleitet werden. `sp audit verify` prüft die Kette offline über den Chiffretext. Das passt zu einem Aufbau, bei dem der Agent eine Aktion anfordert, aber selbst keine Repository-Zugangsdaten erhält.

Hash-Verkettung macht spätere Änderungen erkennbar. Sie macht einen schwachen Ereignisdatensatz jedoch nicht vollständig. Protokolliere Repository- und Commit-Identitäten an der Aktionsgrenze. Ein perfekt geprüfter Datensatz mit dem Eintrag «HTTP-Anfrage gesendet» sagt dir nicht, ob die Anfrage den falschen Pull Request erstellt hat.

## Zugangsdaten außerhalb des Agents halten und Erstellung von Merge trennen

Der Agent sollte niemals ein weitreichendes Repository-Token in seinem Prompt, seiner Umgebung, einer Workspace-Datei oder einer Tool-Ausgabe halten. Sobald das Token in diesen Kontext gelangt, kann es über Logs, Shell-Historie, Fehlermeldungen, kopierte Transkripte oder eine Anweisung offengelegt werden, die den Agent dazu bringt, es auszugeben. Eine nachträgliche Maskierung bringt ein Geheimnis nicht zuverlässig wieder unter Kontrolle.

Verwende ein Aktions-Gateway oder einen eng begrenzten Controller, der eine konkrete Anfrage akzeptiert: Erstelle einen Branch in diesem Repository, pushe diese Commits in den erlaubten Präfix, erstelle einen Draft-Pull-Request gegen diesen freigegebenen Basis-Branch oder fordere diese Reviewer an. Das Gateway injiziert die Zugangsdaten und gibt das Ergebnis zurück. Es sollte Aufrufe ablehnen, die nicht in den erklärten Umfang passen.

Erstellungsrechte und Merge-Rechte sind unterschiedliche Privilegien. Ein Dienst, der Pull Requests erstellen kann, darf möglicherweise Tausende schlechter Änderungen vorschlagen. Ein Dienst, der mergen kann, kann eine einzige schlechte Änderung in die Produktion bringen. Kombiniere beides nicht, nur weil eine Demo dadurch reibungsloser läuft. Lass die Merge-Aktion bei geschützten Branch-Regeln und einem verantwortlichen Menschen, selbst wenn der Agent einen fehlerfreien Patch erzeugt hat.

Eine Autorisierung pro Lauf ist ebenfalls besser als ein dauerhaft vertrauenswürdiger Agent-Prozess. Agent-Tools können Unterprozesse starten, in unerwarteten Aufgaben wiederverwendet werden und nach dem Ende der Aufgabe weiterlaufen, die den Zugriff gerechtfertigt hat. Gib einem Prozess eine begrenzte Sitzung, protokolliere die Freigabe und mache den Widerruf sofort wirksam.

## Eine Fehlerübung zeigt Lücken, die Richtlinientexte verbergen

Führe eine kontrollierte Fehlerübung durch, bevor du dich auf die automatische Erstellung von Pull Requests verlässt. Verwende ein Test-Repository oder eine kurzlebige Branch-Richtlinie und gib dem Agent eine Aufgabe, die versucht, jede Grenze zu überschreiten. Es geht darum, Ablehnungs- und Protokollierungsverhalten zu prüfen, nicht darum, eine erfolgreiche Demo zu bewundern.

Beginne mit einem erlaubten und einem verbotenen Repository. Bestätige, dass der Controller im ersten den erwarteten Draft-Pull-Request erstellen kann und das zweite ablehnt, bevor überhaupt ein Branch erscheint. Versuche anschließend einen Push nach `main`, einen Force-Push in einen `agent/`-Branch und einen Pull Request gegen einen nicht freigegebenen Release-Branch. Prüfe Repository-Ereignisse, nicht nur Controller-Meldungen.

Simuliere danach einen Timeout, nachdem die Erstellungsanfrage die Repository-API erreicht hat. Starte den Lauf neu und bestätige, dass er den ursprünglichen Pull Request findet, statt einen weiteren zu öffnen. Ändere den Branch, nachdem du die Testergebnisse gespeichert hast, und prüfe, dass der Wechsel zu «ready» an der SHA-Abweichung stoppt.

Widerrufe schließlich die Agent-Sitzung während eines Laufs und versuche einen weiteren API-Aufruf. Der Aufruf sollte fehlschlagen, die Ablehnung sollte im Entscheidungsdatensatz erscheinen und keine Zugangsdaten dürfen in der Agent-Ausgabe sichtbar sein. Wenn einer dieser Tests davon abhängt, dass ein Mensch eine Chat-Nachricht bemerkt, existiert die Kontrolle noch nicht.

Die erste praktische Maßnahme ist eine Bestandsaufnahme des Tokens, das dein Agent heute verwendet. Liste jedes Repository auf, das er erreichen kann, jeden Branch, den er aktualisieren kann, und ob er mergen oder Einstellungen ändern darf. Die meisten Teams stellen fest, dass das Token weiter reicht als die Aufgabe. Reduziere diesen Umfang, bevor du den Agent um einen weiteren Pull Request bittest.
