Repository navigation
Description
Problem
Every PR that changes a plugin bumps plugins/<name>/.claude-plugin/plugin.json version and prepends a CHANGELOG.md heading. scripts/check-changelog-parity.sh --check-bump origin/main (.github/workflows/ci.yml, step "Verify a version bump updates the plugin's CHANGELOG.md") requires the PR's version to be above main's, and "Fail when behind base on overlapping paths" (scripts/check-stale-base-overlap.sh) fails a PR that is behind main on a path it also changed. plugin.json and CHANGELOG.md are the paths every PR on a plugin shares, so each PR that lands on the same plugin forces every other open PR on it to merge main, renumber, rewrite its CHANGELOG heading, and re-run CI.
This was raised before as #464 and #1746. Both closed as not planned (#1746: "Re-open with a concrete acceptance brief if a consumer needs the work"). The merge rate has grown since, and this issue is that brief.
Evidence
Measured 2026-10-03.
- Merge rate:
gh pr list --state merged --base main --search "merged:>=2026-10-02"returns 84 PRs merged between 2026-10-02T01:29Z and 2026-10-03T04:51Z, about 3 per hour. - Of the 40 most recently merged PRs, 33 change at least one
plugin.json. Together they carry 112 "Merge ... main" commits; 25 have two or more and 9 have four or more. Median time open is 2.3 h (p90 5.9 h). - Worst cases: #5935 14 base merges in 6.1 h; #5902 9 in 8 h (44 manifests); #5920 6 in 3.8 h; #5992 3 in 1.2 h.
- Not every base merge is a version collision; some are stale-base overlaps on other files. The version and CHANGELOG lines are the one overlap every plugin PR has.
- A PR that conflicts with main gets no
pull_requestruns at all ("Workflows will not run onpull_requestactivity if the pull request has a merge conflict", GitHub docs). Observed on feat(effort): phase effort advice, event-log content opt-in and typed OTEL columns, with the upstream record #5992 head090bfb49f:gh run list --branch feat/spending-your-effort-pr2shows no run for that SHA andmergeablereadUNKNOWN. A collision therefore costs a silent stall as well as a re-run.
Command used for the churn numbers:
gh pr list --repo melodic-software/claude-code-plugins --state merged --base main \
--search "merged:>=2026-10-02" --limit 40 --json number,createdAt,mergedAt,files,commits \
--jq '.[]|[.number,
([.commits[]|select(.messageHeadline|test("^Merge (remote-tracking )?branch .*main"))]|length),
([.files[].path|select(test("^plugins/[^/]+/\\.claude-plugin/plugin\\.json$"))]|length)]|@tsv'Impact
- Each base merge re-runs
ciandtest-windowsand needs a fresh human or agent turn to renumber and rewrite a CHANGELOG heading. - The merge queue now being adopted (feat: adopt GitHub async merge, merge queue, stacked PRs and the new App token format #5935, melodic-software/github-iac#583) does not remove this. Two PRs that both take plugin X from 1.2.0 to 1.2.1 each pass their own PR checks; the second fails to build its merge group on the textual conflict and is ejected, and its author renumbers as today.
Proposed fix options
- CHANGELOG fragments plus release-time bump. A PR adds
plugins/<name>/changes/<pr-or-slug>.mdwith a bump level (patch/minor/major) and its entry text, and never editsversionorCHANGELOG.md. A release workflow (scheduled or on push to main) folds pending fragments intoCHANGELOG.md, bumpsversion, and opens one release PR. This is the changesets model. New files never conflict. Cost: a release workflow, a fragment-format check in place of--check-bump, and a release PR that must itself passci-statusunder thesigningand squash-only rulesets. - Drop
versionfromplugin.json. The plugin loading reference says that with noversionin the manifest or marketplace entry, a relative-path plugin in a Git-hosted marketplace takes the commit SHA of its directory, and "To let users track commits instead, leaveversionout of both the manifest and the entry." No plugin here declares a versioneddependenciesconstraint (grep -A4 '"dependencies"' plugins/*/.claude-plugin/plugin.jsonfinds none) andmarketplace.jsoncarries no versions. CHANGELOG entries would still conflict on top-insert unless they also become fragments, and every main commit becomes an update for every user of that plugin. - Keep per-PR bumps, automate the renumber. A bot or babysit step re-merges main and renumbers on conflict. Fewest repo changes, but it keeps the re-runs and only hides the turn cost.
Recommendation: option 1. It removes the shared lines rather than automating around them, keeps semver for users, and is the only option that stays conflict-free inside a merge queue. Basis: the churn numbers above, and the queue behaviour described under Impact (judgment, from GitHub's documented merge-group build on the combined head).
Acceptance criteria
- A PR that changes a plugin edits neither
versioninplugin.jsonnorCHANGELOG.mddirectly. - Two open PRs on the same plugin can both merge with no base merge between them caused by version or CHANGELOG lines.
--check-bumpand the version-monotonicity checks (check-changelog-parity.sh --check-bump has no version-monotonicity check: regressions and equal versions pass silently #2056, check-changelog-parity: --check-bump compares against the fork point, so a version already taken on main passes #4325) are replaced by a check that every plugin change carries a fragment, and released versions stay monotonic.- Over a week after rollout, the share of merged plugin PRs with two or more base merges falls from 25 of 40 (measured above), re-measured with the same command.
Activity
kyle-sexton commentedon Oct 3, 2026
Another data point from 2026-10-03, same cause:
plugin.json/CHANGELOG.mdfiles the moment fix: clear claude.ai marketplace sync warnings and gate them in the validator #5902 merged, because fix: clear claude.ai marketplace sync warnings and gate them in the validator #5902 had bumped most of the same plugins. None of the conflicts touch the PR's actual change; all are version lines and changelog headings.This supports moving the bump out of the PR (computed at merge or release time from per-PR fragments or commit metadata) so concurrent PRs on the same plugin never touch the same lines.
kyle-sexton commentedon Oct 3, 2026
The accepted fix for this is ADR 0048 (merged in #6015): each PR adds a changelog fragment instead of bumping
plugin.jsonandCHANGELOG.md, and a bot-maintained release PR does the bumps. This issue now tracks carrying it out.Phases (see the ADR for each phase's rollback):
workflow_dispatchonlyplanning,claude-ops,source-control,disk-hygiene); turn on the scheduleSettled decisions (ADR, Resolved decisions):
workflow_dispatchuntil the phase 2 pilot proves the workflow's merge step.bump: none.The merge queue on
main(ruleset 24422263) went live on 2026-10-03. Its first two merges (#6094, #6105) went through without a CHANGELOG ejection.kyle-sexton commentedon Oct 3, 2026
More evidence for this issue, measured 2026-10-03 over a wider window than the body's 40 PRs.
Last 100 merged PRs (#5875, merged 2026-10-02T19:01Z, through #6121, merged 2026-10-03T20:09Z), with the same headline regex as the body (
^Merge (remote-tracking )?branch .*main), commits and files read over REST:plugins/*/.claude-plugin/plugin.jsonThe regex misses differently worded merges such as
Merge origin/main into ..., so these counts are a lower bound.PRs from today's CI work that went through repeated merge-main rounds:
plugin.json. Its merges came fromci.ymloverlaps with concurrent CI PRs, so the stale-base overlap gate produces the same churn on shared infrastructure files too.The silent stall happened again. While each of these PRs was in conflict, no
pull_requestworkflow ran, and nothing showed that CI was waiting until someone merged main. That matches the GitHub docs quote in the body.These numbers give the acceptance criterion's "share of merged plugin PRs with two or more base merges" a 100-PR baseline: 49 of 80 (61%). Re-measure with the same window size after phase 4 of ADR 0048.
kyle-sexton commentedon Oct 4, 2026
This was generated by AI during triage.
Routing: human-gated. Work class: structural (C4). Priority: high.
Why: the design decision is settled. ADR 0048 is on main with
Status: accepted, and the 2026-10-03 comment turns this issue into its five-phase rollout. What is left is not delegable as one item:Priority is high because the current per-PR bump serializes concurrent PRs on the same plugin (the 100-PR baseline in the comments: 49 of 80 plugin PRs needed two or more base merges).
Suggested next step: break the rollout into one item per phase with
/work-items:decompose, so the parts that need no human (phase 1 tooling, with no plugin opted in) can be routed separately from the GitHub App setup and the pilot. Nostatus: needs-decision: nothing about the design is open, and the blocker is the capability and class.Finding: the title still describes the problem, not the work. Something like "ci: carry out ADR 0048 (changelog fragments and a bot-maintained release PR)" would match what the issue now tracks.
Brief (phase-level contract, from the ADR):
versionin its manifest nor itsCHANGELOG.md; a bot-maintained release PR applies the bumps.versionfrom manifests (option 2) beyond the version-omission probe the ADR requires before phase 2.kyle-sexton commentedon Oct 4, 2026
Version-omission probe (ADR 0048, Resolved decisions 5) run 2026-10-04 on Claude Code 2.1.289: both plugins update, so the ADR stands and phase 2 is not blocked by the probe.
alpha,beta) and noversionanywhere, tested as a local directory and as a Git-hosted repo over smart HTTP. A commit that changed onlyalphamoved both to the new HEAD SHA (claude plugin update betareportedupdated from 4498f2af7489 to b02d5c93a41calthoughbeta's last directory commit was5b27beb), and the cache gained a full copy of each per HEAD.version: a content change with no bump was not delivered (already at the latest version (1.0.0)); bumping onlyalphato 1.1.0 updated onlyalpha.versionwould refresh every relative-path plugin for every user on every merge to main, which is the ADR's reason for rejecting that option. Explicit per-plugin versions remain required.versionis "The commit SHA of the installed directory". In practice that is the marketplace repo's HEAD, not the last commit that touched the plugin directory.Phase 1 tooling (empty opt-in list) is in progress as a draft PR. Phase 2 needs the dedicated release GitHub App.
kyle-sexton commentedon Oct 4, 2026
Phase 1 is in draft #6328: tooling only, with an empty opt-in list. An independent review found the logic matches the ADR. These Gates-table items are outside phase 1's rollout list and must land before the phase 2 pilot:
check-changelog-parity.sh --check-bump(lines 93-96): count a consumed fragment as the change. Today a release PR touches only CHANGELOG, the version field and.changes/, so it fails BUMP WITHOUT CHANGE. That would keep the pilot'sci-statusred.--check-requiredcurrently exempts CHANGELOG and version-onlyplugin.jsonedits for every PR. In a scratch test, a non-release PR that bumped a listed plugin by hand and added no fragment passed.--checkand--check-requiredinto the changelog job, and--check-releaseinto the release PR andmerge_group.release-plugins.shorders fragments by commit, and feat(scripts): add changelog-fragment tooling with no plugin opted in #6328 now refuses to run in a shallow clone.fix(scripts): harden fragment creator and release ordering from review
kyle-sexton commentedon Oct 4, 2026
More evidence, 2026-10-04 (Pocock upstream sync batch):
CHANGELOG.mdand/orplugin.jsonat the first base merge: fix(source-control): make the PR body gate agree with CI on code-only sections #6270, fix(review): dispatch only named read-only agents so review subagents cannot recurse #6307, fix(work-items): keep won't-do blockers blocking and ignore foreign PRs as in-flight work #6310 (twice in one ready pass), fix(planning): drop the one-seam absolutism and sweep the wayfind map after each resolution #6295 and fix(wizard): set the shell var in write_env, keep a symlinked .env, survive TERM=dumb #6275. fix(wizard): set the shell var in write_env, keep a symlinked .env, survive TERM=dumb #6275 conflicted again onplugins/wizard/CHANGELOG.mdafter itsci-statushad already passed.pull_requestCI run. fix(work-items): keep won't-do blockers blocking and ignore foreign PRs as in-flight work #6310's lint fix heade5461e409had zero workflow runs until it was re-merged.gh pr merge --squash --auto --match-head-commit <sha>so GitHub merges the momentci-statuspasses.fix(scripts): catch edited consumed fragments and stage release writes
11 remaining items
kyle-sexton commentedon Oct 29, 2026
Re-triage. Every criterion is met by merged work, so this closes as completed.
ADR 0048 phases, all merged:
Each criterion on origin/main:
versionnorCHANGELOG.mddirectly.scripts/fragment-plugins.txtlists every plugin directory underplugins/(it also namespr-pipeline, which has no directory there)..github/workflows/pr-require-checks.yml:1865-1867runscheck-changelog-fragments.sh --check-requiredon every PR. The comment at:1720-1728says "only the release pull request may move a plugin's version or add a CHANGELOG heading" and that "Every plugin is in fragment mode, so no other pull request bumps a version.".changes/<plugin>/. Measured over the last 100 merged PRs (fix(pr-pipeline): route pr-unblock stops to a failing status, not needs-human, unless a person must decide #10252-feat(testing): resolve grouped feature ids in run-e2e #10744, merged 2026-10-28): 18 were release PRs, and none of the other 82 changed aversionvalue or added a##CHANGELOG heading. Three of them touched those files: feat(desktop-notification): forward mod toasts to native notifications #10658 and refactor(testing): route run-e2e through /app-control:drive with suggestive onboarding #10715 edited only option text inplugin.json, and docs(education): reword the CHANGELOG lines shared with pstack teach #10651 reworded existing changelog lines. Base merges still happen, because the ready flow merges the base by design, but no version or CHANGELOG line can cause them now.--check-bumpreplaced by a fragment check, with released versions monotonic.--check-requiredis the fragment check (:1865-1867).--check-bumpnow runs only to keep the release PR's bumps monotonic and documented (:1722-1728), and--check-releaseguards the release PR and the merge queue (:1895-1897).Closing as completed.
github-actions commentedon Oct 29, 2026
The triage sweep dispatched intake triage for this issue (attempt 1 of 2).
github-actions commentedon Oct 29, 2026
Proposed work class: work-class: structural
Basis: automated intake triage; the lanes App applied this class.
Size: Large
Complexity: High
Uncertainty: Known
Flags: none
Blocked by: none
Understood: per-PR plugin version and CHANGELOG bumps made every plugin PR collide with main at the current merge rate. The issue proposes changelog fragments with a release-time bump. A later maintainer comment reports the fragment/release flow is already merged and every acceptance criterion is met, so this looks ready to close as completed. No duplicates found among the open issues. Nothing further is needed from the reporter.
Automated triage, applied by the lanes App.
melodic-automation-lanes commentedon Oct 29, 2026
Not admitted to the issue lane yet: Size large: the split into sub-issues at intake is pending (owner Q93).
melodic-automation-lanes commentedon Oct 29, 2026
Split at intake (owner Q93). This issue is Size Large, so its done-when is split into 7 sub-issues, each holding its task-list lines as written.
Automated split by the lanes App. Admission waits on this issue while a sub-issue is open; work-unblock closes it as completed once every sub-issue is closed by a merge or a listed human.