fix(cd): make bump-and-update-marketplace resilient to concurrent merges - #592
jack-nsheaps[bot] wants to merge 2 commits into
Conversation
The bump-and-update-marketplace job checks out main at the trigger SHA, makes bump + marketplace commits, then does a bare `git push`. When feature PRs merge to main in rapid succession, main advances after checkout and the push is rejected with "non-fast-forward", silently losing the marketplace update (observed: cd run 27215378913 failed this way). Replace the bump/marketplace/push tail with a sync-and-retry loop: on each attempt resync to the latest origin/main, recompute the (fully derived) version bumps and marketplace.json, run lint, commit, and push. Self-healing — if a concurrent run already bumped a plugin and advanced cd/last-release, the recompute produces no further changes. auto-bump's "already bumped" guard still preserves manual version bumps. Mirrors the same fix applied to nsheaps/agents. https://claude.ai/code/session_01Er2WCDMWrTNZNq1N6ykTSD
Plugin Version PreviewPreview only — plugin versions and
|
git add aborts the entire command (exit 128) if any literal pathspec matches no files, staging nothing. With 2>/dev/null || true, an empty plugins/*/CHANGELOG.md glob would silently stage neither plugin.json nor marketplace.json, breaking out of the loop with "no changes" — the exact silent-loss this PR fixes. Split into independent `git add` calls so a non-matching glob is harmless. Addresses review feedback on PR #592. https://claude.ai/code/session_01Er2WCDMWrTNZNq1N6ykTSD
There was a problem hiding this comment.
👍 Ready to merge — the silent-skip footgun from my last review is fixed
✅ git add all-or-nothing footgun resolved in 87bd8a1 — each pathspec now staged independently, so an empty CHANGELOG.md glob can't abort the whole stage (resolved thread)
✅ Sync-and-retry loop correctly resolves the non-fast-forward race; recompute-from-scratch is idempotent and self-healing
✅ set -euo pipefail interplay with if/|| guards re-verified safe
✅ Simpler than the multi-step git-auto-commit-action chain it replaces
🖱️ Click to expand for full details
git add all-or-nothing footgun resolved in 87bd8a1 — each pathspec now staged independently, so an empty CHANGELOG.md glob can't abort the whole stage (resolved thread)✅ Sync-and-retry loop correctly resolves the non-fast-forward race; recompute-from-scratch is idempotent and self-healing
✅
set -euo pipefail interplay with if/|| guards re-verified safe✅ Simpler than the multi-step
git-auto-commit-action chain it replacesCorrectness & approach ✅
The diagnosis remains accurate: the old tail checked out main at the trigger SHA, committed, and did a bare git push with no sync — any merge that landed after checkout caused a non-fast-forward rejection, silently losing the marketplace update. The replacement — resync (fetch --tags --force + reset --hard origin/main) → recompute → stage → commit → push origin HEAD:main, retried up to 5× — is the correct shape.
The idempotency argument holds: both the bumps (derived from the cd/last-release diff via auto-bump-plugins, honoring its "already bumped" guard) and marketplace.json (regenerated by update-marketplace) are fully derived. After a reset --hard onto a main already containing a concurrent run's bump commit, recomputing produces no further changes → git diff --cached --quiet is true → clean break. Self-healing as described. ✅
Fix verification ✅
The one issue from review #2 is resolved. The current head (87bd8a1) stages each pathspec in its own tolerant git add:
git add plugins/*/.claude-plugin/plugin.json 2>/dev/null || true
git add plugins/*/CHANGELOG.md 2>/dev/null || true
git add .claude-plugin/marketplace.json 2>/dev/null || true
A non-matching CHANGELOG.md glob now only fails its own git add (swallowed by || true); plugin.json and marketplace.json still stage correctly. This closes the exact silent-loss class the PR targets. ✅
Control-flow safety ✅
Re-traced the set -euo pipefail interaction: git diff --cached --quiet and git push both sit in if conditions (non-zero won't trip set -e); the three git adds use || true; git fetch/reset/commit are intended hard-fail points. No accidental early exits. Folding the two prior lint steps into a single pre-stage lint is correct (auto-fixes captured by the subsequent git add) and simpler. ✅
Security 🔒
N/A for scoring — workflow only; contents: write and the force-push to main/tag are unchanged from the prior implementation. No new surface.
Scores
- Code Quality 100% — clean, well-commented, correct; the prior deduction is resolved.
- Simplicity 95% — meaningfully simpler than the
git-auto-commit-actionchain it replaces. - Security N/A — CI plumbing, no auth/permission change.
- Confidence 95% — logic traced end-to-end and the fix verified in the head commit; residual 5% is that the loop itself can only be exercised on a real push to
main, not in this PR (job is skipped here).
Recommended follow-ups (non-blocking):
- The final
Update release tagstep force-pushescd/last-releasetoHEADwithout its own resync; ifmainadvances again after the loop's push, the tag briefly lags the true tip (self-corrects next run). Pre-existing; could be folded into the retry loop later. git push origin HEAD:mainfailures are all treated as "main advanced"; a persistent non-race failure (auth/network) would burn all 5 attempts before erroring. Acceptable, but a fetch-and-compare to distinguish non-fast-forward from other errors would fail faster.
Footnotes
-
Workflow Run: nsheaps/ai-mktpl actions run 27240420640 ↩
-
git addpathspec semantics — git-add documentation ↩
Problem
The
bump-and-update-marketplacejob incd.yamlfails on push tomainat the Push all changes step when feature PRs merge in rapid succession:It checks out
mainat the trigger SHA, makes bump + marketplace commits, then does a baregit pushwith no sync/retry. Ifmainadvances after checkout, the push is rejected and the marketplace update is silently lost. Observed in cd run 27215378913.Fix
Replace the bump → marketplace → push tail with a sync-and-retry loop (up to 5 attempts). Each attempt: resync to the latest
origin/main(fetch --tags --force+reset --hard), recompute bumps against the stablecd/last-releasebase, regeneratemarketplace.json, runlint, stage only the allowed files, commit, andgit push origin HEAD:main; on rejection it resyncs and retries.Both bumps and
marketplace.jsonare fully derived, so recomputing from the latestmaineach attempt is safe and self-healing: a concurrent run that already bumped a plugin and advancedcd/last-releasesimply yields no further changes.auto-bump's "already bumped" guard preserves manual bumps.This mirrors the identical fix already merged in
nsheaps/agents(#234), which was validated end-to-end — the merge's own CD run succeeded and corrected existing marketplace drift.Validation
cd.yamlparses as valid YAML; both jobs (version-preview,bump-and-update-marketplace) intact.main(cannot be fully reproduced in a PR, where the job is skipped).🤖 Opened by an AI agent on Nate's behalf.
https://claude.ai/code/session_01Er2WCDMWrTNZNq1N6ykTSD