fix(ci): backport merge commits to master instead of opening a spurious PR - #3696
Merged
Conversation
…us PR
A PR merged into live-docs with the merge button puts a merge commit in
the pushed range, and cherry-pick refuses one without -m ("is a merge but
no -m option was given"). The step treats any nonzero exit as a conflict,
so it opened a PR saying "resolve the conflicts before merging" against a
branch with none, and the per-commit fallback loop then hit the same error
and warned the merge was "not included" when its content was already there.
It fired on every merge-commit push so far: #3657, #3691, #3695.
Pick the first-parent list with -m 1, so a merged PR is one patch: the
merge's own net change. -m 1 is a no-op on a non-merge, so squash-merge
pushes are unaffected.
Two smaller corrections in the same step, both observed on those PRs: the
conflict wording now belongs to the conflict reason alone rather than to
every unexpected failure, and a branch whose conflicts were resolved by
keeping the markers now names those files in the body — #3657 shipped 130
markers across 14 files, including start-sdk's StartSdk.ts, and GitHub
still reported it mergeable.
dr-bonez
approved these changes
Aug 12, 2026
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.
live-docs→ master backport has opened a PR on every merge-commit push and only those: #3657, #3691, #3695. None of them was a merge-commit conflict.What happens
The bulk pick is over the whole pushed range:
git cherry-pick -x -n $(git rev-list --reverse "$RANGE")Merge a PR into
live-docswith the merge button and that range contains a merge commit, which cherry-pick refuses:The step treats any nonzero exit as
fallback=conflict, so the PR body says "resolve the conflicts before merging" — for #3695 GitHub reportedmergeable: true. The per-commit fallback loop then hits the identical error and adds the merge toDROPPED, producing "These commits could not be carried onto this branch at all and are not included" about content the branch already had.A squash-merge push (
aa6e6fa93, the same day as #3695) landed straight on master, which is why this looked intermittent.The fix
Pick the first-parent list with
-m 1, so one merged PR is one patch — the merge's own net change against the previouslive-docstip.The alternatives are both worse, and I measured rather than argued (harness builds six repos, primes
masterto the push'sBEFORE, and compares each strategy's index tree against an oracle):--no-merges-m 1alone--first-parent+-m 1--no-mergesis the tempting one-word fix and it is the dangerous one: it drops a merge's own content and commits a wrong tree to master with rc=0 and no warning.-m 1on a non-merge is a no-op (accepted since git 2.21), so the squash path is untouched.Replayed against the real ranges:
aa6e6fa93..8bfa0eb5f(docs: backport live-docs 8bfa0eb5f to master #3695) →rc=0, tree6288567cad7a251059feb8e71d72f7cce051e15e, and master'sprojects/start-docs/then matcheslive-docsbyte for byte. It would have landed unattended.7695ab1ff..ef3649d3d(docs: backport live-docs ef3649d3d to master #3691) →rc=0.5a64e43c1..7695ab1ff(docs: backport live-docs 7695ab1ff to master #3657) → stillrc=1. That one was a genuine content conflict, and it still falls back, correctly.The fallback loop takes the same list so the branch carries what was actually tried against master; on #3695's range
DROPPEDis now empty.Two smaller corrections in the same step
Both are things those three PRs actually did.
The conflict wording belonged to
*). Any unexpected cherry-pick failure inherited "resolve the conflicts". It now belongs toconflict), and anything else says which reason fired and points at the run log.A branch resolved by keeping the markers now says so. The loop deliberately
git add -As conflict markers for a human to fix, but nothing told the human. #3657's branch shipped 130 markers across 14 files — includingprojects/start-sdk/lib/StartSdk.ts,shared-libs/ts-modules/start-core/lib/util/getRootCa.tsandAGENTS.md— and GitHub reported itMERGEABLE. The body now names them under a[!CAUTION].Two details there are load-bearing:
--all-matchis the per-file conjunction (-e A --and -e Basks for both on one line and finds nothing), and the|| trueis required becausegit grepexits 1 on no match, which underset -euo pipefailwould abort the step beforegh pr createon every clean fallback.Testing
bash -non all threerun:blocks, prettier clean, and an end-to-end harness over the real ranges plus the fallback loop and the marker probe under the step's own shell options — all pass. There is no way to exercise the realcreateCommitOnBranchpath without pushing tolive-docs, so the first merge-commit push after this lands is the end-to-end proof.Deliberately not here
Found while reading, none of it needed to stop the spurious PRs, each with its own blast radius:
additions=$(…)assembles the whole payload through argv andbase64s each file into a jq argument — a large backport can exceed the per-argument limit, andcore.quotePathwould corrupt a non-ASCII path. Real, but a rewrite of the payload assembly.if: steps.pick.outputs.fallback != '', so a hard failure in the pick step skips the safety net entirely and the change is dropped with a red X.contentionis reported for everycreateCommitOnBranchfailure, not just the compare-and-swap loss it names.concurrency: cancel-in-progress: falsestill cancels pending runs, so two quicklive-docsmerges can lose a range permanently. Probably the most valuable follow-up.docs-sync-on-tag.yml'sSource-Commitscan wants--full-history.Happy to take any of these as separate PRs.
Re: #3695
It is clean, docs-only, and content-complete — its branch and
live-docsare identical underprojects/start-docs/, and the patched pick reproduces the same tree. Worth merging rather than closing: there is noworkflow_dispatchhere, and the next push'sgithub.event.beforewill be8bfa0eb5f, so those commits never appear in a range again.