Ein Changelog-Check für GitHub Actions
5 Min. Lesezeit
Jedes Team, das einen Changelog von Hand pflegt, hat schon dasselbe Gespräch nach demselben Vorfall geführt: Ein Release ist ohne Eintrag rausgegangen, jemand fragt warum, und die ehrliche Antwort ist, dass die Person, die ihn geschrieben hätte, schnell unterwegs war und der Changelog-Schritt nur im Gedächtnis existierte. Changelog-Automatisierung behandelt, was eine Pipeline sicher automatisieren kann und was noch einen Menschen braucht; ein Changelog-Check in CI ist die andere Hälfte dieses Problems, weil das Automatisieren des Schreibens nicht hilft, wenn niemand verpflichtet ist, es überhaupt auszulösen. GitHub Actions ist da, wo die meisten Teams ihre Pull-Request-Checks ohnehin schon laufen lassen, also lebt auch dieser dort.
Warum scheitert „wir bitten die Leute, einen Eintrag hinzuzufügen” nach einem vorhersehbaren Muster?
Weil es in einem Pull Request mit allem anderen um Aufmerksamkeit konkurriert, und es der einzige Teil ohne unmittelbare Konsequenz beim Auslassen ist. Tests scheitern laut und blockieren den Merge. Ein fehlender Changelog-Eintrag blockiert nichts, also verliert er in der Praxis sobald jemand in Eile ist, was meistens der Fall ist. Eine Regel, die durch Erinnerung durchgesetzt wird, verfällt genau im erwartbaren Tempo: gut für die ersten paar Wochen nach der Vereinbarung, dann still aufgegeben, sobald die Person, die sich darum kümmerte, in den Urlaub geht oder das Team wechselt.
Was prüft ein CI-Check für einen Changelog-Eintrag tatsächlich?
Nicht die Qualität des Textes, nur ob ein Eintrag existiert und wohlgeformt ist, was der richtige Umfang für einen Changelog-Check ist, der in CI läuft statt im Kopf einer Person. Eine übliche Form: Der Check schaut sich das Diff des PR an und verlangt entweder eine neue Datei in einem Changeset-Verzeichnis (das Muster, das Changesets und ähnliche Tools nutzen) oder eine geänderte Zeile in einer Changelog-Datei, und lässt den Build scheitern, wenn keins von beiden existiert. Die Prüfung, was der Eintrag tatsächlich sagt, passiert weiter dort, wo sie immer passierte, im Code Review, weil dieses Urteil nicht in ein Skript gehört.
| Was der CI-Check prüft | Was er nicht prüft |
|---|---|
| Ein Changeset oder eine Changelog-Zeile existiert im Diff | Ob der Text klar ist |
| Der Eintrag referenziert das richtige Paket, in einem Monorepo | Ob eine Änderung überhaupt einen Eintrag verdient |
| Die Datei ist syntaktisch gültig (Frontmatter, JSON-Form) | Ob der Eintrag ehrlich über die Auswirkung ist |
Braucht jeder PR einen, oder sind manche Änderungen ausgenommen?
Manche sind ausgenommen, und die Ausnahmeliste ist die Stelle, an der solche Systeme meist
tatsächlich gebaut oder aufgegeben werden. Ein Dependency-Bump ohne sichtbaren Effekt, eine
reine Test-Änderung, ein interner Refactor ohne Verhaltensänderung: Keiner davon sollte eine
Beitragende zwingen, einen Changelog-Eintrag für etwas zu erfinden, das niemandem beim Lesen des
Changelogs auffällt. Das funktionierende Muster ist ein Label oder Flag, das eine Beitragende
setzen kann (no-changelog-needed) und den CI-Check ohne Datei erfüllt, geprüft von wer auch
immer den PR genehmigt, sodass die Ausnahme selbst dieselbe Prüfung durchläuft, die ein Eintrag
hätte.
Was passiert bei legitimen Ausnahmen wie einem dringenden Hotfix?
Das Gate gehört an den Merge, nicht an den Deploy: Ein Hotfix unter echtem Zeitdruck kann mit einem Platzhalter-Eintrag oder einem Follow-up-Ticket mergen, vorausgesetzt der CI-Check wird durch Absicht erfüllt statt nur durch einen fertigen Absatz; manche Teams akzeptieren einen einzeiligen Stub, den eine Maintainerin vor dem nächsten Release-Cut ausbaut. Was das Gate nie erlauben sollte, ist das stille Auslassen des Schritts, weil ein Stub, der vergessen wird, ein kleinerer Fehler ist als ein Eintrag, der nie existiert hat, und ein Stub zumindest eine Spur hinterlässt, die später jemand finden kann.
# .github/workflows/changelog-check.yml
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
jobs:
changelog:
if: >-
!contains(github.event.pull_request.labels.*.name,
'no-changelog-needed')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # der Diff braucht den Basis-Branch
- name: Require changelog entry
run: |
base="origin/${{ github.base_ref }}"
if ! git diff --name-only "$base"...HEAD \
| grep -q '^\.changeset/'; then
echo "No changeset. Add one, or have a maintainer"
echo "apply the no-changelog-needed label."
exit 1
fi
Woher wisst ihr, dass der Check selbst korrekt ist, bevor er echte PRs blockiert?
Öffnet zuerst einen Wegwerf-Pull-Request gegen einen Wegwerf-Branch: einen mit Changeset, einen
ohne, und einen mit dem Ausnahme-Label, und bestätigt, dass alle drei das erwartete Ergebnis
liefern, bevor der Check auf die Arbeit anderer angewendet wird. Ein Changelog-Check, der fail-open
läuft und jeden PR durchwinkt, weil eine Bedingung verkehrt herum geschrieben wurde, ist schlimmer
als gar kein Check, weil er wie eine Absicherung aussieht, die es nicht gibt. workflow_dispatch
auf derselben Datei, manuell gegen ein paar kürzlich gemergte PRs ausgeführt, fängt die meisten
dieser Fehler ab, ganz ohne einen echten Pull Request zu brauchen.
Funktioniert dieselbe Idee auch außerhalb von GitHub Actions?
Die Form überträgt sich, nur die Syntax ändert sich. GitLab CI drückt dieselbe Regel als
Job-rules-Block aus, der $CI_MERGE_REQUEST_LABELS statt eines GitHub-Actions-if prüft, und
eine erforderliche Merge-Request-Genehmigung kann den Ausnahme-Review-Schritt ersetzen. Der Check
in diesem Artikel ist GitHub Actions, weil das die Plattform ist, auf der die meisten Leser schon
sind, aber die eigentliche Anforderung, ein maschinell geprüftes Gate statt einer erbetenen
Konvention, ist überall dieselbe, wo CI vor einem Merge läuft.
Funktioniert das in einem Monorepo genauso?
Es braucht ein Teil mehr: für welches Paket der Eintrag ist. Monorepo-Changelogs behandelt, warum eine einzelne repoweite Datei nicht mehr funktioniert, sobald Pakete unabhängig released werden; der CI-Check erbt dieselbe Anforderung; ein Changeset, das kein Paket nennt, ist kein nützlicher Beleg dafür, dass der richtige Changelog aktualisiert wird, nur dass irgendeine Datei irgendwo im Diff geändert wurde. Werkzeuge, die dafür gebaut sind (Changesets ist das übliche im JavaScript-Ökosystem), lassen die Beitragende das betroffene Paket und einen Semver-Bump zur selben Zeit wählen, in der das Changeset erstellt wird, sodass der CI-Check beide Teile kostenlos bekommt statt sie später zu erraten.
FAQ
Sollte der CI-Check den Merge blockieren oder nur warnen? Blockieren. Eine Warnung ist funktional dasselbe wie freundlich bitten, was schon gescheitert ist. Das Ausnahme-Label existiert genau deshalb, damit ein echter Warn-Fall trotzdem einen legitimen Weg durch dasselbe harte Gate hat.
Wer prüft, ob ein Ausnahme-Label korrekt gesetzt wurde? Wer auch immer den Pull Request genehmigt, als Teil derselben Prüfung, die sie ohnehin schon macht. Das Label sollte nie selbst gesetzt und ungeprüft bleiben, sonst wird es dieselbe stille Umgehung, die das Gate schließen sollte.
Ersetzt das Erzwingen in CI die Notwendigkeit einer Changelog-Automatisierungs-Pipeline? Nein, es speist eine. Changelog-Automatisierung behandelt, wie strukturierte Einträge zu einer Seite, einem Feed und einer E-Mail werden; der CI-Check garantiert, dass diese strukturierten Einträge überhaupt existieren, um sie zu automatisieren.
Was ist die kleinste Version davon, die sich zuerst zu bauen lohnt? Ein einzelner Check, der scheitert, wenn keine Datei unter einem festgelegten Changelog-Verzeichnis geändert wurde, mit einem Ausnahme-Label. Paket-Routing und Semver-Ableitung für ein Monorepo können später kommen; die Grundgewohnheit, ein Eintrag existiert oder jemand hat explizit gesagt, dass keiner nötig ist, lohnt sich von Anfang an.
Die technischen Aussagen in diesem Artikel wurden nicht unabhängig geprüft. Wenn etwas nicht stimmt, sagen Sie es uns, und wir korrigieren es.