From e7340aee656500b8efc9fd0fd17f38edffc25cc9 Mon Sep 17 00:00:00 2001 From: Tejas Chopra Date: Thu, 16 Jul 2026 14:28:57 -0700 Subject: [PATCH] =?UTF-8?q?ci(changelog):=20stop=20the=20CHANGELOG=20casca?= =?UTF-8?q?de=20=E2=80=94=20release-please=20owns=20it=20(#2329)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Stops the recurring **CHANGELOG cascade** where several concurrent PRs all edit `CHANGELOG.md`'s `## Unreleased` section, so the first to merge turns the rest `DIRTY` — forcing a serial rebase-per-merge grind. Root cause: **release-please already generates `CHANGELOG.md`** from Conventional Commit titles (see `.release-please-config.json` `changelog-sections`), so the hand-written `## Unreleased` entries are *both redundant with release-please and the sole source of the conflicts*. The PR template and CONTRIBUTING were actively telling contributors to keep hand-editing it. `.gitattributes merge=union` (#2138) does not help — GitHub squash-merge ignores merge drivers. Fix: make release-please the only author of the changelog and stop hand-edits at the source. Closes # ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) — CI/process ## Changes Made - **`.github/workflows/changelog-guard.yml`** (new): fails any PR that modifies `CHANGELOG.md`, except release-please's own release PR (head branch `release-please--*`). Uses the preinstalled `gh` CLI — no third-party action to pin. - **`.github/PULL_REQUEST_TEMPLATE.md`**: flips the "I have updated the CHANGELOG.md" checkbox to "I did **not** edit CHANGELOG.md — release-please generates it from my Conventional Commit PR title." - **`CONTRIBUTING.md`**: replaces step 6 ("Update `CHANGELOG.md`") with the release-please policy. ## Testing - [x] `actionlint .github/workflows/changelog-guard.yml` — clean - [x] YAML parses (`yaml.safe_load`) - [x] Guard match logic unit-checked locally: fires on root `CHANGELOG.md`, ignores nested paths (`docs/CHANGELOG.md`) and PRs that don't touch it. ### Test Output ```text YAML OK actionlint OK root CHANGELOG.md -> FIRES nested docs/CHANGELOG.md -> pass no changelog -> pass ``` ## Real Behavior Proof - Environment: local clone + `actionlint`. - Exact steps: created the workflow, validated with actionlint + a YAML parse, and exercised the `grep -qx 'CHANGELOG.md'` decision against representative changed-file lists. - Observed result: guard fires only on a root-level `CHANGELOG.md` edit; the release-please branch is exempted via the job-level `if`. - Not tested: a live PR run in Actions (will exercise on this PR itself — note this PR does **not** touch `CHANGELOG.md`, so the guard should pass green here). ## Additional Notes - To *enforce* (block merge, not just show red), add **Changelog Guard / no-manual-changelog** to the branch's required status checks. - Follow-up: the ~10 currently-open PRs that hand-edit `CHANGELOG.md` will need that edit removed (a clean deletion now, not a conflict resolution) before they merge — after which the cascade is gone for good. --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/changelog-guard.yml | 38 +++++++++++++++++++++++++++ CONTRIBUTING.md | 2 +- 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/changelog-guard.yml diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a47654668..2014d8dc8 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -54,7 +54,7 @@ Closes # - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes -- [ ] I have updated the CHANGELOG.md if applicable +- [ ] I did **not** edit `CHANGELOG.md` — it is generated by release-please from my Conventional Commit PR title (a CI guard enforces this) ## Screenshots (if applicable) diff --git a/.github/workflows/changelog-guard.yml b/.github/workflows/changelog-guard.yml new file mode 100644 index 000000000..bde07e1c5 --- /dev/null +++ b/.github/workflows/changelog-guard.yml @@ -0,0 +1,38 @@ +name: Changelog Guard + +# CHANGELOG.md is generated by release-please from Conventional Commit titles +# (see .release-please-config.json). Hand-editing it makes every concurrent PR +# conflict on the same `## Unreleased` lines — the "changelog cascade" where one +# merge turns the rest DIRTY. `.gitattributes merge=union` does not help because +# GitHub squash-merge ignores merge drivers. So the fix is to stop hand-edits at +# the source: this guard fails any PR that touches CHANGELOG.md, except +# release-please's own release PR (the one place it is meant to change). +# +# ponytail: uses the preinstalled gh CLI, no third-party action to pin. If a +# rare PR legitimately must edit CHANGELOG.md, a maintainer can merge past this +# non-required check; add a label-based exemption only if that ever recurs. + +on: + pull_request: + +permissions: + contents: read + pull-requests: read + +jobs: + no-manual-changelog: + # release-please's release PR is the sole author of CHANGELOG.md. + if: ${{ !startsWith(github.head_ref, 'release-please--') }} + runs-on: ubuntu-latest + steps: + - name: Reject manual CHANGELOG.md edits + env: + GH_TOKEN: ${{ github.token }} + run: | + if gh pr view "${{ github.event.pull_request.number }}" \ + --repo "${{ github.repository }}" \ + --json files --jq '.files[].path' | grep -qx 'CHANGELOG.md'; then + echo "::error::Do not edit CHANGELOG.md by hand. release-please generates it from your Conventional Commit PR title (e.g. 'fix(proxy): ...'). Remove the CHANGELOG.md change — your entry appears automatically in the next release PR." + exit 1 + fi + echo "OK — CHANGELOG.md not modified." diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cb3692a08..1b6e7b6d7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,7 +77,7 @@ A human maintainer reviews every dep change. PRs that add or bump a package must 3. One logical change per PR. 4. Add tests. 5. `uv run pytest` · `uv run ruff check .` · `uv run ruff format .` -6. Update `CHANGELOG.md` for user-facing changes. +6. Do **not** edit `CHANGELOG.md` — release-please generates it from your Conventional Commit PR title, so a clear `fix(...)`/`feat(...)` title *is* your changelog entry. A CI guard rejects manual edits. 7. Open the PR with a clear description + `Real behavior proof` + any spec/justification required, and keep the PR in draft until the `Review Readiness` boxes are complete. **Title format** (conventional commits): `feat:`, `fix:`, `docs:`, `test:`, `refactor:`.