post

Team-Automatisierung mit GitHub Actions

Warum ich wiederkehrende Teamentscheidungen als ausführbare Workflows behandle – und woran sich zeigt, ob die Automatisierung wirklich trägt.

≈ 8 Min. Lesezeit

Jedes Team trifft dieselben Entscheidungen wieder und wieder. Wird gemergt, bevor die Tests grün sind? Ist der Code formatiert? Hat die Änderung einen Changelog-Eintrag? Wer darf ein Release auslösen? In vielen Teams leben diese Antworten im Kopf einer erfahrenen Person, in einem Wiki oder in einem PR-Kommentar, den man schon dreimal geschrieben hat. Das funktioniert, solange alle wach und anwesend sind. Es bricht an dem Tag, an dem jemand Neues dazukommt, jemand im Urlaub ist oder es einfach spät ist.

GitHub Actions ist für mich zuerst kein CI-Werkzeug, sondern ein Ort, an dem solche Teamvereinbarungen ausführbar werden. Ein Workflow im Repository ist eine Regel, die nicht mehr diskutiert, sondern angewendet wird – bei jedem Push, bei jedem Pull Request, transparent im selben Verlauf, in dem auch der Code liegt. Genau darin liegt der Wert und genau da fangen auch die interessanten Architekturfragen an.

Automatisierung als ausführbare Vereinbarung

Der erste Denkfehler ist, Automatisierung als Zeitersparnis zu verkaufen. Zeit spart sie auch, aber das ist der kleinere Effekt. Der größere ist, dass eine Entscheidung aufhört, verhandelbar zu sein. Wenn „wir mergen nur mit grünen Tests" eine Konvention ist, hält sie so lange, bis der Druck steigt. Wenn dieselbe Regel ein Required Check auf dem Branch ist, hält sie auch dann, wenn niemand hinschaut. Die Regel wandert vom sozialen in den technischen Raum, und dort ist sie robust gegen Müdigkeit und Hierarchie.

Damit verschiebt sich meine Design-Frage. Ich frage nicht „welche Schritte kann ich automatisieren", sondern „welche Entscheidung will ich verlässlich machen". Das ist eine Architekturentscheidung, keine Skriptübung. Ein guter Workflow kodiert eine Absicht: Diese Klasse von Fehlern soll den Hauptzweig nie erreichen. Diese Nacharbeit soll niemand mehr von Hand machen. Dieser Schritt soll nachvollziehbar sein, auch in einem halben Jahr.

Wo die Grenze verläuft

Bevor ich eine Zeile YAML schreibe, ziehe ich eine Grenze, die viele Pipelines nicht sauber ziehen: die zwischen Prüfen und Verändern. Ein Gate liest nur. Es sagt ja oder nein und fasst nichts an – Lint, Tests, Typecheck, Format-Check. Ein verändernder Schritt greift ein: Er publiziert ein Artefakt, taggt ein Release, deployt, kommentiert, labelt. Diese beiden Klassen haben völlig unterschiedliche Anforderungen an Rechte, Wiederholbarkeit und Fehlerverhalten, und wenn man sie im selben Job vermischt, erbt der harmlose Test-Schritt plötzlich die Rechte des gefährlichen Deploy-Schritts.

Meine Faustregel: Prüfschritte laufen auf jedem Pull Request, auch von Forks, und bekommen keine Geheimnisse. Verändernde Schritte laufen nur auf vertrauenswürdigen Auslösern – ein Merge auf den Hauptzweig, ein Tag, ein bewusst ausgelöster Lauf. Diese Trennung ist nicht Bürokratie, sie ist die Sicherheitsarchitektur des ganzen Repositories. GitHub nimmt mir einen Teil davon ab: Bei einem Pull Request aus einem Fork ist das GITHUB_TOKEN read-only und die Secrets sind nicht sichtbar. Das ist kein Zufall, sondern die Antwort auf ein reales Angriffsszenario, und ich baue meine Workflows so, dass sie diese Grenze respektieren statt sie zu umgehen.

Ein konkretes Gate

So sieht ein Pull-Request-Gate aus, das ich in dieser Form immer wieder aufsetze. Es prüft und verändert nichts:

name: pr-gate

on:
  pull_request:
    branches: [main]

jobs:
  verify:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        node-version: [12.x, 14.x]
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - run: npm test -- --ci

Ein paar Entscheidungen darin sind bewusst und tragen mehr Bedeutung, als sie aussehen. npm ci statt npm install, weil ich reproduzierbare Installationen aus dem Lockfile will und keine schleichende Drift der Abhängigkeiten. Die Matrix über zwei Node-Versionen, weil „läuft bei mir" die häufigste Lüge in Teams ist und ich die Kompatibilität lieber vom Rechner in die Pipeline verlege. fail-fast: false, weil ich bei einem roten Lauf beide Versionen sehen will, nicht nur die erste, die abbricht – ein abgebrochener Report kostet eine ganze Feedback-Runde.

Der eigentliche Hebel ist aber nicht das YAML. Er ist die Branch-Protection-Regel, die diesen Check zur Pflicht macht. Erst dadurch wird aus einer freundlichen Empfehlung eine Grenze, die der Hauptzweig verteidigt. Der Workflow liefert das Signal, die Protection-Regel gibt ihm Zähne.

Der teure Fehler ist der glückliche Pfad

Die meisten Pipelines werden für den Fall gebaut, in dem alles gut geht. Interessant wird es genau da, wo es das nicht tut, und dort entscheidet sich, ob die Automatisierung ein Werkzeug oder eine neue Fehlerquelle ist.

Der erste Stolperstein ist Idempotenz. Ein verändernder Schritt muss so gebaut sein, dass ein zweiter Lauf keinen Schaden anrichtet. Läufe werden wiederholt – nach einem Netzwerkfehler, per Re-Run-Knopf, weil jemand denselben Tag neu setzt. Wenn mein Publish-Schritt beim zweiten Mal ein doppeltes Artefakt erzeugt oder mit einem harten Fehler abbricht, habe ich die manuelle Nacharbeit nicht abgeschafft, sondern nur verschoben. Ich baue solche Schritte deshalb so, dass sie prüfen, ob die Version schon existiert, und dann sauber nichts tun.

Der zweite Stolperstein ist der Fork. Ein Pull Request von außen soll geprüft, aber nicht mit Vertrauen belohnt werden. Es gibt pull_request_target, das mit den Rechten des Zielrepositories läuft und Zugriff auf Secrets hat – gedacht für harmlose Aufgaben wie das Labeln oder Kommentieren eines PR. Wer damit fremden Code aus dem Fork auscheckt und ausführt, öffnet die Tür für genau die Geheimnisse, die er schützen wollte. Meine Regel ist stumpf: Auf pull_request_target läuft nur Code aus meinem eigenen Branch und niemals der Inhalt des PR. Das ist keine Schikane, sondern die Trennung von Prüfen und Vertrauen an der einzigen Stelle, an der sie wirklich wehtut.

Der dritte Stolperstein ist der flaky Test. Ein Check, der zufällig rot wird, ist schlimmer als kein Check, weil er dem Team beibringt, das rote Kreuz zu ignorieren. In dem Moment, in dem „einfach nochmal laufen lassen" zur Gewohnheit wird, ist der Wert des Gates weg. Ein instabiler Test ist für mich kein Randproblem, sondern ein Fehler an der Grenze selbst – er gehört repariert oder isoliert, nicht toleriert.

Der Weg von Prüfen zu Verändern

Das Bild dahinter ist einfach, und ich zeichne es bewusst als Fluss, weil die Richtung die ganze Aussage trägt:

flowchart LR
  PR[Pull Request] --> Gate{Gate<br/>lint, test, types}
  Gate -- rot --> Stop[Merge blockiert]
  Gate -- grün --> Merge[Merge auf main]
  Merge --> Trusted[Vertrauenswürdiger Lauf]
  Release --> Audit[Nachvollziehbar im Verlauf]
  Trusted --> Release[Tag, Publish, Deploy]

Links wird nur gelesen, rechts wird erst nach dem Merge verändert. Zwischen beiden liegt die Vertrauensgrenze. Fast jede Sicherheits- und Betriebsfrage, die mir in echten Projekten begegnet, lässt sich auf die Frage zurückführen, ob ein Schritt auf der richtigen Seite dieses Strichs steht.

Der bewusste Auslöser

Nicht jeder verändernde Schritt soll automatisch feuern. Für ein Release will ich oft einen Menschen im Spiel, aber ohne dass jemand lokal Kommandos abtippt, die keiner mitliest. Genau dafür nutze ich workflow_dispatch – einen Auslöser, den man bewusst per Knopfdruck betätigt, mit einer kleinen Eingabemaske:

name: release

on:
  workflow_dispatch:
    inputs:
      level:
        description: "semver bump level"
        required: true
        default: patch

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 14.x
      - run: npm ci
      - run: npm run build
      - run: node ./scripts/release.js --level "${{ github.event.inputs.level }}"
        env:
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

Das ist die manuelle Entscheidung als Formular statt als mündliche Absprache. Die eigentliche Logik liegt bewusst in release.js, einem getesteten Skript, das den Workflow nur noch aufruft – das Secret ist auf diesen einen vertrauenswürdigen Pfad begrenzt und taucht im Prüf-Gate nie auf. Der Vorteil ist nicht die Bequemlichkeit, sondern die Nachvollziehbarkeit: Jedes Release steht mit Auslöser, Zeitpunkt und Person im selben Verlauf wie der Code. Aus einem Vorgang, der früher in einem Terminal verschwand, wird ein Eintrag, den man ein halbes Jahr später noch lesen kann.

Was das messbar bringt

Ich mag keine Automatisierung, die sich nur gut anfühlt. Der Nutzen sollte an drei Stellen sichtbar werden, sonst habe ich Aufwand gegen ein Bauchgefühl getauscht.

Der erste ist die Durchlaufzeit eines Pull Requests. Wenn das Gate die üblichen Rückfragen im Review – ist es formatiert, laufen die Tests, ist der Typecheck sauber – schon vor dem menschlichen Blick beantwortet, verkürzt sich die Zeit von „PR offen" zu „gemergt" spürbar. Das Review kann sich auf das konzentrieren, was eine Maschine nicht sieht: ob die Lösung die richtige ist.

Der zweite ist die Reibung im Review selbst. Diskussionen über Formatierung und Stil sind teuer, nicht weil sie lange dauern, sondern weil sie den Ton vergiften. Eine Maschine, die Format-Fragen entscheidet, nimmt sie aus der zwischenmenschlichen Ebene heraus. Niemand streitet mehr mit einem Linter.

Der dritte ist die Zeit bis zum ersten sinnvollen Beitrag einer neuen Person. Wenn die Regeln des Teams im Repository stehen und beim ersten PR sichtbar werden, muss niemand sie mündlich weitergeben. Onboarding wird von einer Bringschuld erfahrener Kollegen zu einer Eigenschaft des Projekts. Für mich ist das der unterschätzteste Effekt: Automatisierung ist auch eine Form von Dokumentation, die nie veraltet, weil sie ausgeführt wird.

Wann ich es bewusst nicht mache

Ein Werkzeug ist keine Architektur, und GitHub Actions ist keine Ausnahme. Es gibt Fälle, in denen ich abrate. Wenn eine Automatisierung eine echte Orchestrierung über viele Systeme mit Zustand, Wiederaufsetzpunkten und langen Laufzeiten wäre, ist ein Workflow-Runner das falsche Zuhause – das gehört in einen richtigen Job-Runner oder eine dafür gebaute Plattform, nicht in eine CI-Datei. Und wenn eine Regel noch im Fluss ist, weil das Team selbst nicht weiß, ob sie richtig ist, zementiere ich sie nicht in einem Required Check. Automatisierung ist gut darin, geklärte Entscheidungen durchzusetzen, und schlecht darin, ungeklärte zu ersetzen.

Der teuerste Fehler ist, den Runner zur heimlichen Deployment-Plattform wachsen zu lassen, bis kritische Prozesse in einer YAML-Datei liegen, die niemand mehr versteht. Ein Workflow, der eine Seite füllt, ist ein Gewinn. Ein Workflow, der drei Bildschirme füllt und Sonderfälle mit if-Bedingungen abfängt, ist meist ein Programm am falschen Ort – dann gehört die Logik in ein getestetes Skript oder ein kleines CLI, das der Workflow nur aufruft.

Wie ich anfange

In der Praxis mache ich daraus keine große Initiative. Ich fange mit genau einem Gate an, das eine echte, wiederkehrende Reibung wegnimmt – meistens Tests plus Format-Check auf jedem PR – und mache es über die Branch-Protection zur Pflicht. Dann warte ich zwei Wochen und schaue, ob es das Team entlastet oder nur nervt. Erst wenn es trägt, kommt der nächste Schritt dazu, und der erste verändernde Schritt kommt bewusst später als der erste prüfende.

Der Maßstab bleibt derselbe wie bei jeder Architektur: Eine Entscheidung ist erst gut, wenn ich ihren Fehlerpfad beschreiben kann. Was passiert bei einem Re-Run? Was, wenn der PR aus einem Fork kommt? Was, wenn ein Test flaky wird? Wer bemerkt es, wenn das Gate stumm ausfällt? Wenn ich diese Fragen beantworten kann, ist aus der Bequemlichkeit ein Stück verlässliche Teaminfrastruktur geworden. Und genau darum geht es: nicht darum, dass eine Maschine Arbeit erledigt, sondern darum, dass eine Entscheidung endlich hält.

Kommentare