-
Notifications
You must be signed in to change notification settings - Fork 0
fix(ci): make the release push survive a branch that moved mid-run #326
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -65,6 +65,10 @@ jobs: | |||||||||||||||||||||||||||||||||||||
| uses: actions/checkout@v6 | ||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||
| fetch-depth: 0 | ||||||||||||||||||||||||||||||||||||||
| # `workflow_dispatch` pins `github.sha` at dispatch time; a queued run | ||||||||||||||||||||||||||||||||||||||
| # would otherwise bump and changelog from a stale commit. See the same | ||||||||||||||||||||||||||||||||||||||
| # note in publish.yml. | ||||||||||||||||||||||||||||||||||||||
| ref: ${{ github.ref_name }} | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| - name: Setup pnpm | ||||||||||||||||||||||||||||||||||||||
| uses: pnpm/action-setup@v5 | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -177,11 +181,26 @@ jobs: | |||||||||||||||||||||||||||||||||||||
| echo "==> Publishing $TARBALL $COMMON_FLAGS" | ||||||||||||||||||||||||||||||||||||||
| npm publish "$TARBALL" $COMMON_FLAGS | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| - name: Tag + push | ||||||||||||||||||||||||||||||||||||||
| # Same shape as publish.yml: the package is already on npm by now, so the | ||||||||||||||||||||||||||||||||||||||
| # push reconciles onto whatever landed on the branch mid-run rather than | ||||||||||||||||||||||||||||||||||||||
| # failing, and the tag is created only once the commit is on the branch. | ||||||||||||||||||||||||||||||||||||||
| - name: Push release commit | ||||||||||||||||||||||||||||||||||||||
| if: ${{ github.event.inputs.dry_run != 'true' && (github.event.inputs.version != 'none' || github.event.inputs.custom_version != '') }} | ||||||||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||||||||
| BRANCH: ${{ github.ref_name }} | ||||||||||||||||||||||||||||||||||||||
| run: scripts/push-release-commit.sh | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| - name: Tag + push tags | ||||||||||||||||||||||||||||||||||||||
| if: ${{ github.event.inputs.dry_run != 'true' && (github.event.inputs.version != 'none' || github.event.inputs.custom_version != '') }} | ||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||
| git tag -a "personas-core-v${{ steps.bump.outputs.version }}" -m "${{ steps.package.outputs.npm_name }}@${{ steps.bump.outputs.version }}" | ||||||||||||||||||||||||||||||||||||||
| git push origin HEAD --follow-tags | ||||||||||||||||||||||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||||||||||||||||||||||
| TAG="personas-core-v${{ steps.bump.outputs.version }}" | ||||||||||||||||||||||||||||||||||||||
| if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then | ||||||||||||||||||||||||||||||||||||||
| echo "::warning::tag $TAG already exists - leaving it as is" | ||||||||||||||||||||||||||||||||||||||
| exit 0 | ||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||
| git tag -a "$TAG" -m "${{ steps.package.outputs.npm_name }}@${{ steps.bump.outputs.version }}" | ||||||||||||||||||||||||||||||||||||||
| git push origin "refs/tags/$TAG" | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+198
to
+203
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: If another run creates the same tag on origin after checkout, this step still tries to create and push it because Prompt for AI agents
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| - name: Summary | ||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,90 @@ | ||||||||||
| #!/usr/bin/env bash | ||||||||||
| # | ||||||||||
| # Push the release commit at HEAD to $BRANCH, rebuilding it on the branch tip | ||||||||||
| # if the push is rejected. | ||||||||||
| # | ||||||||||
| # Publish workflows run this *after* the packages are already on npm, so a | ||||||||||
| # rejected push is the worst failure available: the registry moves ahead of git | ||||||||||
| # and the next run bumps from a version the branch has never seen. That is what | ||||||||||
| # happened on 2026-08-24 (run 32713237250), when a queued second publish run | ||||||||||
| # built from a stale dispatch SHA and lost the push race with the run ahead of | ||||||||||
| # it. Anything landing on the branch mid-run does this — a merged PR, another | ||||||||||
| # release commit — so reconcile instead of failing. | ||||||||||
| # | ||||||||||
| # HEAD must be a single release commit with a parent. Callers that create | ||||||||||
| # several commits per run need a different shape and should not use this script. | ||||||||||
| # | ||||||||||
| # Env: | ||||||||||
| # BRANCH branch to push to (default: main) | ||||||||||
| # PUSH_ATTEMPTS how many pushes to make before giving up (default: 5) | ||||||||||
| # | ||||||||||
| set -euo pipefail | ||||||||||
|
|
||||||||||
| BRANCH="${BRANCH:-main}" | ||||||||||
| ATTEMPTS="${PUSH_ATTEMPTS:-5}" | ||||||||||
|
|
||||||||||
| for attempt in $(seq 1 "$ATTEMPTS"); do | ||||||||||
| if git push origin "HEAD:refs/heads/$BRANCH"; then | ||||||||||
| echo "Pushed the release commit to $BRANCH on attempt $attempt." | ||||||||||
| exit 0 | ||||||||||
| fi | ||||||||||
|
|
||||||||||
| # Every rebuild must get a push of its own, so stop rebuilding once the last | ||||||||||
| # attempt has been spent rather than leaving a commit that never gets tried. | ||||||||||
| if [ "$attempt" -eq "$ATTEMPTS" ]; then | ||||||||||
| break | ||||||||||
| fi | ||||||||||
|
|
||||||||||
| echo "::warning::push to $BRANCH was rejected (attempt $attempt) - rebuilding the release commit on the current tip" | ||||||||||
| REL=$(git rev-parse HEAD) | ||||||||||
| if ! git rev-parse -q --verify "$REL^" >/dev/null; then | ||||||||||
| echo "::error title=Release commit not pushed::HEAD has no parent, so there is no release commit to rebuild. Reconcile $BRANCH by hand." >&2 | ||||||||||
| exit 1 | ||||||||||
| fi | ||||||||||
| MSG=$(git log -1 --format=%B "$REL") | ||||||||||
| git fetch origin "$BRANCH" | ||||||||||
|
|
||||||||||
| # Exactly the files this release commit changed, taken from its own diff. | ||||||||||
| # A pattern over the tree would also pick up files the release never touched | ||||||||||
| # and revert them — another package's version bumped by whatever landed on | ||||||||||
| # the branch mid-run, say. `--diff-filter=d` drops paths the commit deleted, | ||||||||||
| # which cannot be checked out of it. | ||||||||||
| FILES=$(git diff --name-only --diff-filter=d "$REL^" "$REL") | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: When the newer branch tip deletes a release-owned file, this retry restores it because Prompt for AI agents
Suggested change
|
||||||||||
| if [ -z "$FILES" ]; then | ||||||||||
| echo "::error title=Release commit not pushed::The commit at HEAD adds or modifies no files. Packages may already be on npm; reconcile $BRANCH by hand." >&2 | ||||||||||
| exit 1 | ||||||||||
| fi | ||||||||||
|
|
||||||||||
| git diff --name-only --diff-filter=D "$REL^" "$REL" | | ||||||||||
| while read -r removed; do | ||||||||||
| echo "::warning::$removed was deleted by the release commit; the rebuild does not re-apply that deletion" | ||||||||||
| done | ||||||||||
|
|
||||||||||
| # $FILES is deliberately unquoted below so it splits into one argument per | ||||||||||
| # path. Release files are package.json / CHANGELOG.md paths with no spaces; | ||||||||||
| # noglob keeps the shell from expanding any of them as a pattern. | ||||||||||
| set -f | ||||||||||
|
|
||||||||||
| # This run's copy of a file it owns overwrites the tip's. That is right for a | ||||||||||
| # concurrent release commit and wrong for a hand-edited changelog, so name the | ||||||||||
| # overlap instead of losing it silently. | ||||||||||
| git diff --name-only "$REL^" "origin/$BRANCH" -- $FILES | | ||||||||||
| while read -r changed; do | ||||||||||
| echo "::warning::$changed also changed on $BRANCH during this run - this run's copy wins" | ||||||||||
| done | ||||||||||
|
|
||||||||||
| git reset --hard "origin/$BRANCH" | ||||||||||
| git checkout "$REL" -- $FILES | ||||||||||
| git add -- $FILES | ||||||||||
| set +f | ||||||||||
|
|
||||||||||
| if git diff --cached --quiet; then | ||||||||||
| echo "$BRANCH already carries this run's release files; nothing to push." | ||||||||||
| exit 0 | ||||||||||
| fi | ||||||||||
| git commit -m "$MSG" | ||||||||||
| sleep $((attempt * 5)) | ||||||||||
| done | ||||||||||
|
|
||||||||||
| echo "::error title=Release commit not pushed::Packages are on npm but $BRANCH could not be updated in $ATTEMPTS attempts. Reconcile the workspace versions on $BRANCH by hand." >&2 | ||||||||||
| exit 1 | ||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Persona reconcile reverts unrelated package versions
This step runs push-release-commit.sh without setting
RELEASE_RE, so it uses the default regex (scripts/push-release-commit.sh:29) matching every package'spackage.jsonandCHANGELOG.md, yet this run bumps only personas-core. On a rejected push the reconcile checks out the run-start copies of all those files over the branch tip, reverting anything that landed mid-run. A concurrent main publish that bumped every package is rolled back in git while npm keeps the new versions — the split brain this change set out to fix.Was this helpful? React with 👍 or 👎 to provide feedback.