fix: use atomic git push in plugin npmpublish workflow#7494
Merged
JohnMcLear merged 1 commit intodevelopfrom Apr 8, 2026
Merged
fix: use atomic git push in plugin npmpublish workflow#7494JohnMcLear merged 1 commit intodevelopfrom
JohnMcLear merged 1 commit intodevelopfrom
Conversation
The plugin publish workflow ran `git push --follow-tags` after `pnpm version patch`. `--follow-tags` is non-atomic per ref: if a concurrent publish run won the race, the branch fast-forward would be rejected but the tag push would still land — leaving a dangling `vN+1` tag with no matching version-bump commit on the branch. Every subsequent push would then fail forever with `npm error fatal: tag 'vN+1' already exists`, because `pnpm version patch` would re-derive the same tag name from the unchanged `package.json`. On 2026-04-08, a single churn day (badge fixes + Dependabot merges firing back-to-back) put ~46 plugins into this state simultaneously. Recovery required hand-bumping `package.json` past the dangling tag on every affected repo, twice (a second wave appeared after the first sweep finished, racing the next wave of publishes). Fix: use `git push --atomic origin <branch> <tag>` so the branch update and the tag update succeed or fail as a single server-side transaction. A rejected branch push now also rejects the tag push, the run aborts cleanly, and the next workflow tick can retry against the up-to-date refs without leaving any orphaned tags. Also derive the new tag name from `package.json` after the bump (rather than parsing pnpm version's stdout, which has historically varied) and pass it explicitly into the push. Adds a backend regression test that asserts the workflow file uses `--atomic`, does not contain a literal `git push --follow-tags` command (ignoring the historical comment), and includes both the branch ref and the freshly-bumped tag in the atomic push. The test gates against accidental reverts. This file is the source of truth that `bin/plugins/checkPlugin.ts` propagates into every `ether/ep_*` plugin's `.github/workflows/`, so the next `update-plugins` cron tick will roll the fix out across all plugins automatically. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review Summary by QodoUse atomic git push in plugin npmpublish workflow
WalkthroughsDescription• Replace non-atomic git push --follow-tags with git push --atomic to prevent dangling version tags • Derive tag name explicitly from package.json after version bump instead of parsing stdout • Add regression test to prevent accidental reversion to non-atomic push behavior • Fix addresses critical issue affecting ~46 plugins simultaneously on 2026-04-08 Diagramflowchart LR
A["pnpm version patch"] -->|bumps version| B["package.json"]
B -->|extract tag| C["NEW_TAG variable"]
C -->|atomic push| D["git push --atomic<br/>branch + tag"]
D -->|single transaction| E["Success or<br/>complete failure"]
F["Regression test"] -->|validates| D
F -->|prevents revert| G["No dangling tags"]
File Changes1. bin/plugins/lib/npmpublish.yml
|
Code Review by QodoNew Review StartedThis review has been superseded by a new analysisⓘ The new review experience is currently in Beta. Learn more |
Review Summary by QodoUse atomic git push in plugin npmpublish workflow
WalkthroughsDescription• Replace non-atomic git push --follow-tags with git push --atomic to prevent dangling tags - Fixes race condition where concurrent publishes leave orphaned version tags - Derives tag name explicitly from package.json instead of parsing pnpm stdout • Add regression test to prevent accidental reversion of atomic push behavior • Update workflow permissions comment to reflect atomic push semantics Diagramflowchart LR
A["pnpm version patch"] --> B["Extract NEW_TAG from package.json"]
B --> C["git push --atomic origin branch tag"]
C --> D["Single server transaction"]
D --> E["No dangling tags on race loss"]
F["Regression test"] --> G["Assert --atomic usage"]
G --> H["Prevent future reverts"]
File Changes1. bin/plugins/lib/npmpublish.yml
|
Code Review by Qodo🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewⓘ The new review experience is currently in Beta. Learn more |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
git push --follow-tagswithgit push --atomic origin <branch> <tag>inbin/plugins/lib/npmpublish.yml, the source-of-truth template thatcheckPlugin.tspropagates into everyether/ep_*plugin's.github/workflows/.package.jsonafterpnpm version patch, instead of relying on parsing pnpm's stdout.src/tests/backend/specs/npmpublish-workflow.ts— a regression test that asserts the workflow uses--atomic, has no literalgit push --follow-tagscommand (ignoring forensic comments), and includes both the branch ref and the freshly-bumped tag in the atomic push.Why
git push --follow-tagsupdates the branch and tag refs in two separate server-side transactions. If a concurrent publish run wins the race, the branch fast-forward gets rejected — but the tag push still lands. That leaves a danglingvN+1tag with no matching version-bump commit on the branch, andpackage.jsonstill at versionN.Every subsequent push then runs
pnpm version patch, derives the samevN+1tag name from the unchangedpackage.json, and dies withnpm error fatal: tag 'vN+1' already exists. The plugin can never publish again until someone hand-reconciles the repo.On 2026-04-08, a single churn day (mass badge fixes + Dependabot auto-merge from #7493 firing back-to-back across ~80 plugins) put ~46 plugins into this state at once. The recovery required hand-bumping
package.jsonpast the dangling tag on every affected repo, twice — a second wave appeared after the first sweep finished, racing the next wave of publish runs that the sweep itself had triggered.git push --atomicmakes branch + tag a single server-side transaction. A rejected branch fast-forward also rejects the tag push, the run aborts cleanly, and the next workflow tick retries against up-to-date refs with no orphans left behind.Propagation
This file is the canonical template; the daily
update-plugins.ymlcron in this repo runscheckPlugin.tsagainst every plugin, which copies this file into each plugin's.github/workflows/npmpublish.yml. So one commit here heals every plugin on the next cron tick (orworkflow_dispatch).Test plan
pnpm run ts-check— cleannpx mocha --import=tsx tests/backend/specs/npmpublish-workflow.ts(3 passing)update-plugins.ymlmanually and confirm the new file is propagated to plugin reposNotes
The end-to-end behaviour of
git push --atomicagainst the actual Git server isn't unit-testable (atomicity is a server feature, not local). The regression test is a static-analysis check that the workflow keeps using--atomic, which catches the most likely regression: someone reverting the change in a refactor without realizing why it matters. The forensic comment in the workflow file explains the historical bug for anyone reading the diff later.🤖 Generated with Claude Code