Skip to content

fix(ci): backport merge commits to master instead of opening a spurious PR - #3696

Merged
dr-bonez merged 1 commit into
masterfrom
fix/docs-backport-merge-commits
Aug 12, 2026
Merged

fix(ci): backport merge commits to master instead of opening a spurious PR#3696
dr-bonez merged 1 commit into
masterfrom
fix/docs-backport-merge-commits

Conversation

@helix-nine

@helix-nine helix-nine commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

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-docs with the merge button and that range contains a merge commit, which cherry-pick refuses:

error: commit 8bfa0eb5f4a66633eadc0ac55b9eb71fbd677ebb is a merge but no -m option was given.
fatal: cherry-pick failed

The step treats any nonzero exit as fallback=conflict, so the PR body says "resolve the conflicts before merging" — for #3695 GitHub reported mergeable: true. The per-commit fallback loop then hits the identical error and adds the merge to DROPPED, 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 previous live-docs tip.

The alternatives are both worse, and I measured rather than argued (harness builds six repos, primes master to the push's BEFORE, and compares each strategy's index tree against an oracle):

current --no-merges -m 1 alone --first-parent + -m 1
merge-button push, clean spurious PR ok ok ok
genuine conflict falls back falls back falls back falls back
squash-merge push ok ok ok ok
patch already hand-applied to master spurious PR ok ok ok
merge carrying its own content spurious PR rc=0, WRONG TREE ok ok
merge that resolved a conflict spurious PR spurious PR spurious PR ok

--no-merges is 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 1 on a non-merge is a no-op (accepted since git 2.21), so the squash path is untouched.

Replayed against the real ranges:

The fallback loop takes the same list so the branch carries what was actually tried against master; on #3695's range DROPPED is 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 to conflict), 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 — including projects/start-sdk/lib/StartSdk.ts, shared-libs/ts-modules/start-core/lib/util/getRootCa.ts and AGENTS.md — and GitHub reported it MERGEABLE. The body now names them under a [!CAUTION].

Two details there are load-bearing: --all-match is the per-file conjunction (-e A --and -e B asks for both on one line and finds nothing), and the || true is required because git grep exits 1 on no match, which under set -euo pipefail would abort the step before gh pr create on every clean fallback.

Testing

bash -n on all three run: 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 real createCommitOnBranch path without pushing to live-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 and base64s each file into a jq argument — a large backport can exceed the per-argument limit, and core.quotePath would corrupt a non-ASCII path. Real, but a rewrite of the payload assembly.
  • The fallback step is 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.
  • contention is reported for every createCommitOnBranch failure, not just the compare-and-swap loss it names.
  • concurrency: cancel-in-progress: false still cancels pending runs, so two quick live-docs merges can lose a range permanently. Probably the most valuable follow-up.
  • docs-sync-on-tag.yml's Source-Commit scan 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-docs are identical under projects/start-docs/, and the patched pick reproduces the same tree. Worth merging rather than closing: there is no workflow_dispatch here, and the next push's github.event.before will be 8bfa0eb5f, so those commits never appear in a range again.

…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
dr-bonez merged commit e1da45c into master Aug 12, 2026
6 checks passed
@dr-bonez
dr-bonez deleted the fix/docs-backport-merge-commits branch August 12, 2026 21:42
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.

2 participants