From e7d1fa4a21e2198a1dc9d70b07dbfff8cf83191b Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Sun, 5 Jul 2026 11:05:58 -0700 Subject: [PATCH] Disable auto-merge for unstable GitHub PRs (#7415) Prevent enabling auto-merge when a PR is in an UNSTABLE merge state. GitHub auto-merge mutations reject UNSTABLE PRs directly instead of allowing them to wait, so we should suppress the option. --- .../pr-sidebar/pr-auto-merge-availability.ts | 8 +++++++- .../src/components/github-pr-merge-state.test.ts | 12 ++++++++++++ src/shared/github-pr-auto-merge-availability.test.ts | 8 +++++++- src/shared/github-pr-auto-merge-availability.ts | 8 +++++++- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/mobile/src/components/pr-sidebar/pr-auto-merge-availability.ts b/mobile/src/components/pr-sidebar/pr-auto-merge-availability.ts index d6040f87e2b..5453a0209b6 100644 --- a/mobile/src/components/pr-sidebar/pr-auto-merge-availability.ts +++ b/mobile/src/components/pr-sidebar/pr-auto-merge-availability.ts @@ -15,6 +15,10 @@ function isConflicting(item: MobilePRAutoMergeAvailabilityInput): boolean { return item.mergeable === 'CONFLICTING' || item.mergeStateStatus === 'DIRTY' } +function isUnstable(item: MobilePRAutoMergeAvailabilityInput): boolean { + return item.mergeStateStatus === 'UNSTABLE' +} + function hasReviewRequirement(item: MobilePRAutoMergeAvailabilityInput): boolean { return item.reviewDecision === 'REVIEW_REQUIRED' || item.reviewDecision === 'CHANGES_REQUESTED' } @@ -27,7 +31,9 @@ function canMergeImmediately(item: MobilePRAutoMergeAvailabilityInput): boolean } function canRequestWhenReady(item: MobilePRAutoMergeAvailabilityInput): boolean { - if (item.state !== 'open' || isConflicting(item)) { + // Why: GitHub auto-merge waits on unmet requirements; UNSTABLE is rejected + // by the mutation rather than becoming a waitable auto-merge request. + if (item.state !== 'open' || isConflicting(item) || isUnstable(item)) { return false } if (item.mergeQueueRequired === true) { diff --git a/src/renderer/src/components/github-pr-merge-state.test.ts b/src/renderer/src/components/github-pr-merge-state.test.ts index 9284f52729e..a2f52f8fd31 100644 --- a/src/renderer/src/components/github-pr-merge-state.test.ts +++ b/src/renderer/src/components/github-pr-merge-state.test.ts @@ -62,6 +62,18 @@ describe('presentGitHubPRMergeState', () => { }) }) + it('does not offer enable auto-merge for GitHub unstable PRs', () => { + expect( + presentGitHubPRMergeState( + pr({ + mergeable: 'UNKNOWN', + mergeStateStatus: 'UNSTABLE', + checksSummary: { state: 'failure', total: 1, passed: 0, failed: 1, pending: 0 } + }) + ).autoMergeAction + ).toBeNull() + }) + it('does not offer enable auto-merge on conflicting PRs (GitHub would reject it)', () => { expect( presentGitHubPRMergeState(pr({ mergeable: 'CONFLICTING', mergeStateStatus: 'DIRTY' })) diff --git a/src/shared/github-pr-auto-merge-availability.test.ts b/src/shared/github-pr-auto-merge-availability.test.ts index 47989de6b6d..2e22a3bd3f2 100644 --- a/src/shared/github-pr-auto-merge-availability.test.ts +++ b/src/shared/github-pr-auto-merge-availability.test.ts @@ -48,7 +48,7 @@ describe('github PR auto-merge availability', () => { expect(canShowGitHubPRAutoMergeControl(pr({ autoMergeEnabled: true }))).toBe(true) }) - it('suppresses closed, draft, disallowed, and conflicting PRs', () => { + it('suppresses closed, draft, disallowed, conflicting, and unstable PRs', () => { expect( canShowGitHubPRAutoMergeControl(pr({ state: 'draft', mergeStateStatus: 'BLOCKED' })) ).toBe(false) @@ -63,5 +63,11 @@ describe('github PR auto-merge availability', () => { expect( canShowGitHubPRAutoMergeControl(pr({ mergeable: 'CONFLICTING', mergeStateStatus: 'DIRTY' })) ).toBe(false) + expect( + canEnableGitHubPRAutoMerge(pr({ mergeable: 'UNKNOWN', mergeStateStatus: 'UNSTABLE' })) + ).toBe(false) + expect( + canShowGitHubPRAutoMergeControl(pr({ mergeable: 'UNKNOWN', mergeStateStatus: 'UNSTABLE' })) + ).toBe(false) }) }) diff --git a/src/shared/github-pr-auto-merge-availability.ts b/src/shared/github-pr-auto-merge-availability.ts index 5c7417d622f..67b6e25ad72 100644 --- a/src/shared/github-pr-auto-merge-availability.ts +++ b/src/shared/github-pr-auto-merge-availability.ts @@ -18,6 +18,10 @@ function isConflicting(item: GitHubPRAutoMergeAvailabilityInput): boolean { return item.mergeable === 'CONFLICTING' || item.mergeStateStatus === 'DIRTY' } +function isUnstable(item: GitHubPRAutoMergeAvailabilityInput): boolean { + return item.mergeStateStatus === 'UNSTABLE' +} + function hasReviewRequirement(item: GitHubPRAutoMergeAvailabilityInput): boolean { return item.reviewDecision === 'REVIEW_REQUIRED' || item.reviewDecision === 'CHANGES_REQUESTED' } @@ -30,7 +34,9 @@ function canMergeImmediately(item: GitHubPRAutoMergeAvailabilityInput): boolean } function canRequestWhenReady(item: GitHubPRAutoMergeAvailabilityInput): boolean { - if (!isOpenPR(item) || isConflicting(item)) { + // Why: GitHub auto-merge waits on unmet requirements; UNSTABLE is rejected + // by the mutation rather than becoming a waitable auto-merge request. + if (!isOpenPR(item) || isConflicting(item) || isUnstable(item)) { return false } if (item.mergeQueueRequired === true) {