Skip to content

Resolve CHANGELOG-only conflicts in open PRs automatically - #144

Merged
NinjaLikesCheez merged 3 commits into
ci/skip-passed-inputsfrom
ci/changelog-auto-merge
Oct 19, 2026
Merged

NinjaLikesCheez merged 3 commits into
ci/skip-passed-inputsfrom
ci/changelog-auto-merge

Conversation

@NinjaLikesCheez

Copy link
Copy Markdown
Contributor

Stacked on #143. Review and merge that first; this PR's base then moves to main.

Why

When one PR merges, every other open PR that added a CHANGELOG entry conflicts in CHANGELOG.md, usually nowhere else, and the fix is nearly always "keep both". After a release, every open PR conflicts, because [Unreleased] was emptied.

What it does

scripts/changelog-merge.ts: a structured three-way merge of CHANGELOG.md, not git's line union (which can duplicate ### Fixed, or interleave entries that span several lines). Given base / branch (PR) / main:

  • It starts from main's file and applies the branch's [Unreleased] changes. Entries the branch added go at the end of their section, and a missing section is created in Keep a Changelog order, never twice. An entry both sides added is kept once. An entry the branch reworded or removed is changed only if main still has it untouched.
  • A release in between is handled. Main's [Unreleased] is empty or new, and the branch's added entries land there, not in the version that was cut.
  • It refuses, leaving the conflict to a person, when:
    • both sides changed the same entry
    • the branch edited an entry that has since been released
    • either side has a section twice, an unknown ### heading, or prose that isn't a list entry
    • both sides changed the intro, the released versions or the link footer
    • a side still has conflict markers

.github/workflows/changelog-merge.yml: runs on push to main touching CHANGELOG.md. It also runs on workflow_run after Release, because the bump commit is pushed with GITHUB_TOKEN and so doesn't fire push. Plus workflow_dispatch. For each open, same-repo PR that edits CHANGELOG.md, it does git merge --no-commit origin/main in a worktree:

  • no conflict → nothing to do
  • other files conflict too → left alone
  • only CHANGELOG.md → resolve, commit, push (never forced), and comment
  • refused → abort, and comment with the reason

There's one comment per PR, edited in place. The job summary lists each PR's outcome.

Local: just changelog-merge in a stopped merge or rebase (a rebase is detected, so the sides swap correctly). just changelog-merge-driver registers it as a git merge driver for this clone only, via .git/info/attributes, so nothing is committed.

Edge cases and how they're handled

Case Handling
A release went out between the PR's base and main Branch's new entries go into the new [Unreleased]; editing a released entry → refuse
Both sides add to the same section Main's entries first, then the branch's; one heading
Branch adds a section main doesn't have Inserted in Added/Changed/Deprecated/Removed/Fixed/Security order
Same entry on both sides (cherry-pick, or the PR already merged main) Kept once
Both sides reworded the same entry Refuse + comment
Duplicate ### Fixed already on a side (a bad earlier merge) Refuse; a person fixes it
Multi-line or multi-paragraph bullets Treated as one entry (bullet + indented lines)
Conflicts outside CHANGELOG.md Untouched; GitHub's own conflict banner covers it
Fork PRs Skipped (can't push there, and they're untrusted)
Author pushes while the job runs Non-forced push is rejected; the next main push retries
Untrusted PR content Only main's resolver runs. PR branches are only git merged (no install, build or hooks; .gitattributes can't name a driver without our config)
Pushes with GITHUB_TOKEN don't trigger CI Uses a GitHub App token when CHANGELOG_BOT_CLIENT_ID / CHANGELOG_BOT_PRIVATE_KEY are set; otherwise falls back and the comment says CI needs a re-push or close/reopen
Main's merge brings in .github/workflows/ changes GITHUB_TOKEN can't push that ("workflows" permission); the App has it. Without the App the push is refused and logged
Unsigned bot commits Fine today (the ruleset is disabled). If signed commits are ever required, the App would need to commit through the API

Setup needed (maintainer)

Create a GitHub App with Contents, Pull requests and Workflows: read and write, and install it on pixelbar/pixel. Then set the repo variable CHANGELOG_BOT_CLIENT_ID and the secret CHANGELOG_BOT_PRIVATE_KEY. The runbook's "Changelog conflicts" section covers it. It works without the App, with the caveats above.

Verified

🤖 Generated with Claude Code

Comment thread scripts/changelog-merge.ts
@NinjaLikesCheez
NinjaLikesCheez added this pull request to stack #145 October 19, 2026 07:39
NinjaLikesCheez and others added 2 commits October 19, 2026 09:49
When main's CHANGELOG.md changes (or a Release cuts it), a workflow merges
main into each open same-repo PR that edits it. If CHANGELOG.md is the only
conflict, scripts/changelog-merge.ts does a structured three-way merge of
[Unreleased]: both sides' entries kept, each section once, entries after a
release land in the new [Unreleased]. It refuses (and comments why) when
both sides changed the same entry, a released entry was edited, or the file
isn't plain Keep a Changelog. Only main's resolver runs; PR code never does.
just changelog-merge / changelog-merge-driver do the same locally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Changelog-only pushes skip just check, so a test that reads CHANGELOG.md
could go stale without running. Use a fixture shaped like it instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@NinjaLikesCheez
NinjaLikesCheez force-pushed the ci/changelog-auto-merge branch from f5283f0 to b4655ab Compare October 19, 2026 07:51
A branch entry was only checked against main's [Unreleased], so one that
had landed through another PR and been released since came back as
unreleased. Check the released versions too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@NinjaLikesCheez
NinjaLikesCheez merged commit c056a15 into main Oct 19, 2026
13 checks passed
@NinjaLikesCheez
NinjaLikesCheez deleted the ci/changelog-auto-merge branch October 19, 2026 08:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant