Skip to content

fix(ci): use GraphQL REBASE for auto-update PR branches (kill merge bubbles)#411

Merged
github-actions[bot] merged 2 commits into
mainfrom
fix/auto-update-use-rebase
May 23, 2026
Merged

fix(ci): use GraphQL REBASE for auto-update PR branches (kill merge bubbles)#411
github-actions[bot] merged 2 commits into
mainfrom
fix/auto-update-use-rebase

Conversation

@coseto6125
Copy link
Copy Markdown
Owner

Symptom

PRs #403 / #406 had auto-merge enabled. As main moved (PRs #401/#402/#404 etc. landed), the `Auto-update PR branches` workflow merged main HEAD INTO each PR branch as a merge commit (e.g. b972586), breaking linear rebase history maintained by force-pushes. Another session diagnosed live by force-pushing clean rebases to overwrite the polluted state.

Root cause

The REST endpoint `PUT /repos/{owner}/{repo}/pulls/{pull_number}/update-branch` is merge-only. Per GitHub REST docs, the only body parameter is `expected_head_sha` — no rebase option.

A previously-suggested fix of `-f "update_method=rebase"` doesn't actually work: empirical test on this repo's PRs shows the parameter is silently ignored. The endpoint always produces a merge commit.

Fix

GitHub's GraphQL `updatePullRequestBranch` mutation accepts `updateMethod: REBASE` (enum `PullRequestBranchUpdateMethod` with values MERGE | REBASE, verified empirically against the live schema).

Swap the API call from REST to GraphQL:

```bash

Before

gh api -X PUT "repos/$REPO/pulls/$pr/update-branch"

After

gh api graphql -f query='
mutation($id: ID!) {
updatePullRequestBranch(input: { pullRequestId: $id, updateMethod: REBASE }) {
pullRequest { number headRefOid }
}
}
' -f id="$nodeid"
```

Other changes:

  • Fetch `.id` (GraphQL node ID) alongside `.number` from `gh pr list` (GraphQL mutation requires the node ID).
  • Update response-pattern matching: success = `headRefOid` in response; conflict = `could not be rebased` / `conflict`.
  • Comment marker still applies for conflict cases (one-time notification per conflict).
  • `# shellcheck disable=SC2016` on the GraphQL query line — `$id` inside single quotes is intentional (GraphQL variable, not shell var).

Test plan

  • Self-PR: workflow itself isn't affected (no eligible PR with auto-merge during this PR's lifetime to test against)
  • After merge: next time main moves and an eligible PR exists, verify PR head SHA changes via rebase (clean linear history) instead of merge commit
  • Conflict case: intentionally conflict an open PR with main, watch for marker comment

Notes

  • If GitHub's rebase can't proceed cleanly (e.g., PR already has merge commits from prior auto-update runs), the mutation errors out → marker comment posted. After this PR lands, existing in-flight PRs may need one-time manual rebase to clear historical merge bubbles before this workflow can keep them clean.

The REST endpoint PUT /repos/{}/pulls/{}/update-branch is merge-only —
it merges main HEAD INTO the PR branch as a merge commit, polluting
the PR's history with a merge bubble per main movement. Observed live
in PRs #403/#406: every time main moved, auto-update added a merge
commit (e.g. b972586), breaking force-pushed linear rebase history.

Other Claude session's proposed fix (`-f "update_method=rebase"` on
the REST call) doesn't work — the parameter is silently ignored. Per
GitHub REST docs the only body param is `expected_head_sha`.

The correct path is GraphQL: the `updatePullRequestBranch` mutation
accepts `updateMethod: REBASE` (enum PullRequestBranchUpdateMethod
verified empirically). Switch the API call:

  # before
  gh api -X PUT 'repos/$REPO/pulls/$pr/update-branch'

  # after
  gh api graphql -f query='
    mutation($id: ID!) {
      updatePullRequestBranch(input: { pullRequestId: $id, updateMethod: REBASE }) {
        pullRequest { number headRefOid }
      }
    }
  ' -f id="$nodeid"

Requires fetching .id (GraphQL node ID) alongside .number from
`gh pr list` — added.

Response-pattern matching updated to match GraphQL output (success
detected via *headRefOid* present in response, conflict via
*could not be rebased* / *conflict*).

Risk: if GitHub's rebase decides the branch can't be cleanly rebased
(e.g. merge commits in PR history), it falls back to merge or errors
out. Currently no PR mixes in merge commits, so this is theoretical.
The marker-comment safety net catches unexpected error states.
$id inside the single-quoted query string is a GraphQL variable name,
not a shell expansion. Bash single-quoting is correct here; the
shellcheck-disable comment + explanatory note makes the intent
explicit for future readers.
@github-actions github-actions Bot enabled auto-merge (squash) May 23, 2026 19:25
@github-actions github-actions Bot merged commit 609bd1b into main May 23, 2026
16 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

ecp impact cache (0 symbols) — internal, used by ecp dev pr-analyze

[]

@github-actions github-actions Bot added the ecp:risk-low ecp signal label May 23, 2026
@coseto6125 coseto6125 deleted the fix/auto-update-use-rebase branch May 24, 2026 22:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ecp:risk-low ecp signal

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant