What the design says
ADR-0125 D1 (docs/adr/0125-release-approval-gate-replaces-the-typed-version.md):
gated on a single predicate … main's @objectstack/cli version is not on npm. That is true only just after a version PR merges, and false on all ~18 other daily landings, so no ordinary merge queues a deployment.
D1/D2 also say that merging the Version Packages PR is the decision to release, and that approving release authorises that decision.
What actually happens
The predicate stays true from the version-PR merge until the publish finishes. So every landing in that window queues its own Publish <v> to npm (awaiting approval) deployment. The publish job's concurrency group (release-publish-${{ github.ref }}, cancel-in-progress: false) keeps at most one pending job, so each new landing cancels the deployment that was waiting before it. The job checks out github.sha of its push, so whatever is approved ships main's head at that moment, not the version commit.
Observed on the 17.5.0 release (2026-09-29):
| Release run |
head |
publish job |
| 36533007563 |
3a89d459 (merge-queue batch: 8c87d26a version commit + 4 PRs) |
waited 06:48, cancelled 06:52 |
| 36533376091 |
7001918e |
waited 06:52, cancelled by the next landing |
| 36536081716 |
0f6dcac5 |
waited 07:21, approved 07:40, this one ships 17.5.0 |
Resulting defects:
- 17.5.0 on npm is not the tree the maintainer decided to release.
git log --first-parent 8c87d26a..0f6dcac5 lists 8 PRs that landed after the version commit and ship inside 17.5.0: 6e3aa75e, a093ce3e, 92fe0814 (breaking, feat(spec)!), 3a89d459, 7001918e, c96beb27, ba4648da, 0f6dcac5. Their 8 changesets are still unconsumed in .changeset/, so the 17.5.0 CHANGELOG omits them and 17.6.0 will announce them as new.
- The approval target moves. A maintainer who opens the approval link and is slower than the merge queue finds it cancelled. Approving becomes a race against the queue.
- Stale approval prompts survive. Run 34308599522 (2026-09-09,
Publish 17.4.0 to npm (awaiting approval), head 2a5424a2) sat waiting for 20 days after 17.4.0 had shipped from another run. On 2026-09-29 it was approved by mistake in place of the 17.5.0 prompt. It was cancelled during Build, so nothing was published. This is the "approval noise" the release.yml header warns about, produced by the same file.
The same mechanism is documented for the bookkeeping lane in ADR-0125 D5 ("GitHub keeps at most one pending run per group … the intervening main pushes evict each other"). It was fixed there and not considered for the publish job's own group.
Proposed fix (for review)
- Make the predicate "this push carries the version bump", not "main's version is absent from npm". In
release-integrity, walk github.event.before..github.sha and find the first first-parent commit where packages/cli/package.json's version changes. Mark publish pending only when such a commit exists in this push's range and its version is absent from npm. Later landings then no longer queue deployments.
- Publish the version commit, not the head. Pass that commit's sha as an output. The publish job checks it out (
ref:) and the guard asserts it is an ancestor of main. The #6170 tripwire then runs against that sha.
- Keep the repair lane (D4) as is, for a partial publish.
- Stale prompts: have
release-integrity (or a small scheduled sweep) report any release deployment still waiting for a version already on npm, so it can be rejected rather than left as a lookalike prompt.
- Operational workaround until then: pause the merge queue between merging the Version Packages PR and approving
release.
This touches .github/workflows/release.yml (not a governed surface) and needs an amendment to ADR-0125 D1 (docs/adr/**, governed surface, Tier H). The ADR change is the maintainer's call.
Generated by Claude Code
What the design says
ADR-0125 D1 (
docs/adr/0125-release-approval-gate-replaces-the-typed-version.md):D1/D2 also say that merging the Version Packages PR is the decision to release, and that approving
releaseauthorises that decision.What actually happens
The predicate stays true from the version-PR merge until the publish finishes. So every landing in that window queues its own
Publish <v> to npm (awaiting approval)deployment. The publish job's concurrency group (release-publish-${{ github.ref }},cancel-in-progress: false) keeps at most one pending job, so each new landing cancels the deployment that was waiting before it. The job checks outgithub.shaof its push, so whatever is approved ships main's head at that moment, not the version commit.Observed on the 17.5.0 release (2026-09-29):
3a89d459(merge-queue batch:8c87d26aversion commit + 4 PRs)7001918e0f6dcac5Resulting defects:
git log --first-parent 8c87d26a..0f6dcac5lists 8 PRs that landed after the version commit and ship inside 17.5.0:6e3aa75e,a093ce3e,92fe0814(breaking,feat(spec)!),3a89d459,7001918e,c96beb27,ba4648da,0f6dcac5. Their 8 changesets are still unconsumed in.changeset/, so the 17.5.0 CHANGELOG omits them and 17.6.0 will announce them as new.Publish 17.4.0 to npm (awaiting approval), head2a5424a2) sat waiting for 20 days after 17.4.0 had shipped from another run. On 2026-09-29 it was approved by mistake in place of the 17.5.0 prompt. It was cancelled during Build, so nothing was published. This is the "approval noise" therelease.ymlheader warns about, produced by the same file.The same mechanism is documented for the bookkeeping lane in ADR-0125 D5 ("GitHub keeps at most one pending run per group … the intervening main pushes evict each other"). It was fixed there and not considered for the publish job's own group.
Proposed fix (for review)
release-integrity, walkgithub.event.before..github.shaand find the first first-parent commit wherepackages/cli/package.json's version changes. Mark publish pending only when such a commit exists in this push's range and its version is absent from npm. Later landings then no longer queue deployments.ref:) and the guard asserts it is an ancestor ofmain. The#6170tripwire then runs against that sha.release-integrity(or a small scheduled sweep) report anyreleasedeployment still waiting for a version already on npm, so it can be rejected rather than left as a lookalike prompt.release.This touches
.github/workflows/release.yml(not a governed surface) and needs an amendment to ADR-0125 D1 (docs/adr/**, governed surface, Tier H). The ADR change is the maintainer's call.Generated by Claude Code