Skip to content

fix(release): publish before syncing main, and stop racing the merge - #296

Merged
EtienneLescot merged 2 commits into
mainfrom
fix/promote-order-and-merge-retry
Aug 7, 2026
Merged

fix(release): publish before syncing main, and stop racing the merge#296
EtienneLescot merged 2 commits into
mainfrom
fix/promote-order-and-merge-retry

Conversation

@EtienneLescot

@EtienneLescotEtienneLescot commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

promote.yml has failed on every run since v1.7.0. Last success: 28734947685 (2026-07-05).

RunTagFailureDelay after gh pr create
31029721368v1.9.0GraphQL: This branch can't be rebased (mergePullRequest)2.9 s
30873203114v1.8.0Pull Request has merge conflicts1.7 s
29706639667 / 29702095523v1.7.0same class

These are not real conflicts. PR #288 (v1.9.0's sync PR) was rebase-merged by hand 24 minutes later, base and head SHAs unchanged, and gh api .../pulls/288/commits shows a linear chain whose first parent is exactly base.sha — a head that descends directly from its base cannot conflict. allow_rebase_merge is true, so it is not a repo setting either. What it is: GitHub computes mergeable / mergeStateStatus asynchronously, and the mutation fails while the state is still UNKNOWN.

The expensive part is not the race, it is where the step sits. "Merge release branch into main" ran before "Trigger build workflow" and "Announce stable on Discord", with no continue-on-error and no if: always() below it. So every failure skipped both:

✗ Merge release branch into main
- Trigger build workflow ← skipped
- Announce stable on Discord ← skipped
- Workflow summary ← skipped

Consequences, verified: v1.7.0, v1.8.0 and v1.9.0 were all built by a manual workflow_dispatch (e.g. run 31029970478, dispatched 2 min 32 s after promote died), and #releases has had no announcement since v1.6.0 — while #rc-testing keeps working, because prerelease.yml puts its announce step last.

What changed

Order. Build trigger and Discord announce now run immediately after the stable tag is pushed. They depend on nothing the sync produces — the build is dispatched with --ref on the stable tag, not on main, so the checkout gets the released snapshot whatever main looks like. Publishing no longer depends on a PR round-trip.

Reliability. The sync now polls mergeStateStatus (30 × 5 s) until it leaves UNKNOWN, then retries gh pr merge up to 5 times with a 15 s backoff.

Failure handling. The sync is continue-on-error: true — the release is already out by the time it runs, so a stuck PR should not redden a successful release. To keep that from hiding anything, the job summary emits a warning block naming the outstanding -sync branch when steps.sync_main.outcome != 'success'.

Related issue

No issue — found while auditing the Linux build/packaging chain. Same family as #148 (winget publishing broken), which this does not fix.

Type of change

  • Bug fix

Release impact

  • No release note needed

Desktop impact

  • Not platform-specific

Screenshots / video

n/a

Testing

CI-only change; the workflow can only be exercised by a real promotion. What I verified locally:

  • YAML parses and the step order is what the diff claims (yaml.safe_load → step 6 Trigger build workflow, 7 Announce stable on Discord, 8 Merge release branch into main with id: sync_main, continue-on-error: True).
  • The retry loop propagates its final failure. This is the trap worth naming: the obvious gh pr merge … && break; sleep 15 form exits 0 after five failed attempts and reports a merge that never happened. Simulated with a merge that always fails → ::error:: then exit 1.
  • The polling loop does not trip set -euo pipefail when the state stays UNKNOWN for all 30 iterations (if … then break; fi, not [[ … ]] && break).

The next promotion is the real test. If the race still bites, the sync now fails alone and the release goes out regardless — which is the point.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Release Process
    • Stable releases are now announced before synchronization begins.
    • Release-branch synchronization with the main branch is more resilient, with mergeability checks and automatic retries.
    • Release completion clearly reports if synchronization remains incomplete.

promote.yml ran the main-sync before the two steps that actually ship the
release. The sync opens a PR and merges it ~3s later, which races GitHub's
asynchronous mergeability computation and has failed every time since
v1.7.0: v1.9.0 got "This branch can't be rebased" 2.9s after create, v1.8.0
"has merge conflicts" after 1.7s. Neither was a real conflict — both PRs
were rebase-merged by hand, unchanged, minutes later.
Because the step had no continue-on-error and nothing below it was
if: always(), each failure silently skipped "Trigger build workflow" and
"Announce stable on Discord". v1.7.0, v1.8.0 and v1.9.0 all shipped with
the build dispatched by hand and no #releases announcement.
Two changes, independent on purpose:
- Order. Build trigger and Discord announce now run right after the stable
tag is pushed, before the sync. They depend on nothing the sync produces
— the build is dispatched with --ref on the stable tag, not on main — so
a stuck PR can no longer suppress the release.
- Reliability. The sync polls mergeStateStatus until it leaves UNKNOWN,
then retries the merge up to 5 times. MERGED is set only by a successful
merge: a bare `&& break` loop exits 0 after five failures and reports a
merge that never happened. The step is continue-on-error since the
release is already out by then, and the job summary says so when it
fails rather than letting it pass unnoticed.
@coderabbitai

coderabbitaiBot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 641810ca-61b3-4d68-968f-bf69fc69d1fe

📥 Commits

Reviewing files that changed from the base of the PR and between e2efbb5 and 5dc0f87.

📒 Files selected for processing (1)
  • .github/workflows/promote.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/promote.yml

📝 Walkthrough

Walkthrough

The promotion workflow dispatches the stable-tag build and announces the release before main synchronization. Synchronization waits for mergeability, retries up to five times, and reports incomplete work without blocking publishing.

Changes

Release promotion flow

Layer / File(s)Summary
Publish and announce before synchronization
.github/workflows/promote.yml
The workflow dispatches the stable-tag build and sends the Discord announcement before main synchronization. Synchronization errors do not block publishing.
Retry and report main synchronization
.github/workflows/promote.yml
The workflow validates the sync PR, polls mergeability, retries rebase merges up to five times, and adds a workflow-summary warning when synchronization remains incomplete.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
participant GitHub Actions
participant Stable-tag build
participant Discord
participant Sync PR
participant GitHub mergeability
GitHub Actions->>Stable-tag build: dispatch stable-tag build
GitHub Actions->>Discord: announce release
GitHub Actions->>Sync PR: locate synchronization PR
GitHub Actions->>GitHub mergeability: poll mergeability
GitHub Actions->>Sync PR: retry rebase merge up to five times
GitHub Actions->>GitHub Actions: report incomplete synchronization
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the primary workflow changes: publishing before main synchronization and preventing merge races.
Description check✅ PassedThe description includes all template sections and provides detailed context, change rationale, impact, and testing information.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/promote-order-and-merge-retry

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/promote.yml:
- Around line 173-175: Update the no-PR branch in the synchronization step of
promote.yml to re-check whether main contains the release branch before exiting.
Return status 0 only when main is confirmed up to date; otherwise emit the
existing synchronization warning and exit 1 so sync_main fails when main remains
behind.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e8542611-3f54-4785-9093-69c79154987a

📥 Commits

Reviewing files that changed from the base of the PR and between e3dbb4c and e2efbb5.

📒 Files selected for processing (1)
  • .github/workflows/promote.yml

Comment thread.github/workflows/promote.yml Outdated
L'échec de `gh pr create` est converti en echo, donc la branche « aucune PR
ouverte » couvrait aussi le cas où la création avait échoué pour une autre
raison. Elle sortait alors en 0 alors que le test d'ascendance du haut venait
d'établir que main ne contient pas la branche : sync_main était reporté en
succès et l'avertissement de fin sautait.
On re-teste l'ascendance plutôt que de supposer — quelqu'un a pu merger la
synchro à la main entre-temps — et on sort en 1 sinon.
Exercé avec git et gh stubbés sur les quatre chemins : création échouée avec
main en retard sort bien en 1, création échouée avec main à jour en 0, et les
deux chemins nominaux ne régressent pas.
@EtienneLescot
EtienneLescot merged commit 2b2c9b7 into mainAug 7, 2026
16 checks passed
@EtienneLescot
EtienneLescot deleted the fix/promote-order-and-merge-retry branch August 7, 2026 10:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@EtienneLescot