Skip to content

Merge sync PRs to prerelease right away and retry failed backports - #2293

Merged
cderv merged 3 commits into
mainfrom
ci/backport-auto-merge
Oct 16, 2026
Merged

cderv merged 3 commits into
mainfrom
ci/backport-auto-merge

Conversation

@cderv

@cderv cderv commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

Since the v1.10 release merge, the automatic backport to prerelease failed for every PR merged to main (#2141, #2227, #2235, #2257, #2258, #2222). The changes reached prerelease only through manual backports later (#2271, #2290), #2141 about two months after it merged.

Root Cause

Most failures were not real divergence between the branches but an ordering race. A sync PR stays open on prerelease until someone merges it, from minutes to many hours. When a second PR to main touches the same file in the meantime, it is cherry-picked onto a prerelease that does not have the first change yet, and conflicts.

A failed backport is never retried, so it then breaks every later PR on the same files: #2227 failed on #2141, #2258 and #2257 on #2235. Only #2222 was a real conflict, as #2272 had already changed the same page on prerelease.

Fix

When the cherry-pick applies cleanly, the workflow now merges the sync PR immediately and publishes prerelease by calling publish.yml, as a merge made with GITHUB_TOKEN does not fire the push event. It publishes the branch tip, not the merge commit, so two syncs landing close together still deploy the latest content.

When the cherry-pick fails, the source PR gets the new backport-failed label, so gh pr list --label backport-failed --state merged lists what is missing from prerelease. A sync PR that fails to merge, because prerelease moved between the cherry-pick and the merge, gets the label too and is left open for a manual merge.

After each sync merge, and after any PR merged by hand into prerelease, the workflow comments /sync-prerelease on the oldest backport-failed PR. A backport that only waited on an earlier change then applies on its own, and the chain moves to the next one. It stops at the first PR that fails again, which needs a manual backport and the label removed afterwards.

No concurrency group is used: GitHub cancels a pending run when another one queues, which would drop a backport silently when several PRs merge within a few minutes.

As sync PRs are no longer reviewed before merging, a PR that must not reach prerelease (like #1983 for v1.9 only) now needs the no-sync-prerelease label before it is merged into main. Leaving the sync PR unmerged is no longer possible.

Direct pushes to main are still not synced. That is left for another PR.

Test Plan

  • zizmor and actionlint on the workflow: no new findings in the CI audit
  • Run the gh queries used by the workflow against this repo, read-only (label lookup, oldest labeled PR, empty result)
  • Create the backport-failed label
  • After merge, the next PR to main gets its sync PR merged and prerelease published
  • After merge, a failed backport gets the label, and a later sync triggers its retry

cderv added 2 commits October 16, 2026 12:51
Since the v1.10 release merge, every auto-backport to prerelease has failed, and the fixes reached prerelease only through manual backports weeks later.
Most failures were an ordering race rather than real divergence: sync PRs stay open for minutes to hours, and a second PR to main touching the same file is cherry-picked onto a prerelease that does not have the first change yet.
#2141 conflicted on a freeze file because the sync PR of #2138 merged 19 seconds after it.
Failed backports are never retried, so each one then breaks every later PR on the same files (#2227 after #2141, #2258 and #2257 after #2235).

The workflow now merges a clean sync PR immediately and publishes prerelease itself, since a merge made with GITHUB_TOKEN does not fire the push event publish.yml listens to.
A failed backport labels the source PR backport-failed, so the missing changes are listable instead of buried in bot comments.
After each sync merge, and after any manual merge into prerelease, the workflow comments /sync-prerelease on the oldest labeled PR, so a backport that only waited on an earlier change applies on its own.

No concurrency group: GitHub cancels a pending run when another queues, which would drop backports silently when several PRs merge within minutes.
The cost of merging without review is that a main-only PR now needs the no-sync-prerelease label before it merges, where it used to be enough to leave the sync PR unmerged.
A sync PR can also fail to merge when prerelease moves between the cherry-pick and the merge.
That backport now gets the backport-failed label too, instead of being missed by the list and the retry.
The open sync PR is left for a manual merge.

Publishing no longer depends on the label cleanup succeeding after the merge, since nothing else publishes a GITHUB_TOKEN merge.
@cderv
cderv force-pushed the ci/backport-auto-merge branch from 7efe69b to 6875e06 Compare October 16, 2026 13:27
@cderv cderv mentioned this pull request Oct 16, 2026
1 of 3 tasks
gh pr list returns at most 30 results by default, newest first, so
with more labeled PRs the sort only saw the newest 30 and the oldest
backport, often the one the others wait on, would never be retried.
@cderv
cderv merged commit 35c61f4 into main Oct 16, 2026
1 check passed
@cderv
cderv deleted the ci/backport-auto-merge branch October 16, 2026 14:11
@github-actions

Copy link
Copy Markdown
Contributor

Successfully created backport PR for prerelease:

github-actions Bot added a commit that referenced this pull request Oct 16, 2026
…2293) (#2295)

* Merge sync PRs to prerelease right away and retry failed backports

Since the v1.10 release merge, every auto-backport to prerelease has failed, and the fixes reached prerelease only through manual backports weeks later.
Most failures were an ordering race rather than real divergence: sync PRs stay open for minutes to hours, and a second PR to main touching the same file is cherry-picked onto a prerelease that does not have the first change yet.
#2141 conflicted on a freeze file because the sync PR of #2138 merged 19 seconds after it.
Failed backports are never retried, so each one then breaks every later PR on the same files (#2227 after #2141, #2258 and #2257 after #2235).

The workflow now merges a clean sync PR immediately and publishes prerelease itself, since a merge made with GITHUB_TOKEN does not fire the push event publish.yml listens to.
A failed backport labels the source PR backport-failed, so the missing changes are listable instead of buried in bot comments.
After each sync merge, and after any manual merge into prerelease, the workflow comments /sync-prerelease on the oldest labeled PR, so a backport that only waited on an earlier change applies on its own.

No concurrency group: GitHub cancels a pending run when another queues, which would drop backports silently when several PRs merge within minutes.
The cost of merging without review is that a main-only PR now needs the no-sync-prerelease label before it merges, where it used to be enough to leave the sync PR unmerged.

* Label sync PRs that fail to merge

A sync PR can also fail to merge when prerelease moves between the cherry-pick and the merge.
That backport now gets the backport-failed label too, instead of being missed by the list and the retry.
The open sync PR is left for a manual merge.

Publishing no longer depends on the label cleanup succeeding after the merge, since nothing else publishes a GITHUB_TOKEN merge.

* Retry the oldest failed backport even past 30 labeled PRs

gh pr list returns at most 30 results by default, newest first, so
with more labeled PRs the sort only saw the newest 30 and the oldest
backport, often the one the others wait on, would never be retried.

(cherry picked from commit 35c61f4)

Co-authored-by: Christophe Dervieux <christophe.dervieux@gmail.com>
Sign up for free to 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