← 输入框|github.com|刷新
Skip to content

Allow authorized users to bypass branch protection when merging - #9144

Open
oleksandr.korol (Dufran) wants to merge 11 commits into
microsoft:mainfrom
Dufran:fix/3996-bypass-branch-protection
Open

oleksandr.korol (Dufran) wants to merge 11 commits into
microsoft:mainfrom
Dufran:fix/3996-bypass-branch-protection

Conversation

@Dufran

@Dufran oleksandr.korol (Dufran) commented Oct 30, 2026 •

Copy link
Copy Markdown

Fixes #3996

Summary

  • Read GitHub’s viewerCanMergeAsAdmin capability rather than infer bypass permission from repository write access.
  • For blocked or out-of-date PRs without the classic capability, query applicable active branch rulesets and their current_user_can_bypass permission. Paginate branch rules, deduplicate ruleset IDs, and support parent rulesets.
  • Offer an unchecked "Bypass branch rules and merge" checkbox in the editor and sidebar, including initially blocked sidebar PRs. Use shared checkbox styling, spacing, and an accessible explanatory note.
  • Require explicit opt-in before exposing the existing merge flow. GitHub remains responsible for authorization through the normal merge mutation.
  • Refresh capability changes and discard stale model/polling results and consent when eligibility changes. Ruleset permissions are not cached; unsupported or failed REST requests fail closed.
  • Preserve conflict, draft, stack, and merge-queue guards. Stack and merge-queue bypass are outside this change.

Ruleset permission limitation

The REST fallback detects permission to bypass any active ruleset applying to the branch, not whether every specific unmet requirement can be bypassed. The UI explains that GitHub will verify all unmet requirements. A merge can still be rejected by GitHub when another required rule is not bypassable.

Validation

  • npm run compile
  • npm run compile:test
  • npx tsc --noEmit -p tsconfig.webviews.json
  • npm run test:webviews — 142 passing
  • npm run test:scripts — 75 passing
  • Focused tests in a real VS Code extension host — 385 passing, 0 failed/pending: GitHubRepository (64), PullRequestModel (135), PullRequestOverview (165), activity bar/sidebar (21)
  • npm run test:preprocess
  • npm run lint
  • npm run hygiene
  • git diff --check

Regression coverage includes REST pagination and permissions, missing/denied/unsupported APIs, permission revocation without caching, stale responses, ruleset fallback gating, initial sidebar propagation, deferred overview races, explicit consent, eligibility guards, and shared checkbox styling.

No live protected-branch merge was performed.

Copilot AI balanced review requested due to automatic review settings October 30, 2026 09:47
@Dufran

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The bypass option can appear before merge-queue requirements finish loading, allowing users to opt into direct merging on branches that require a queue.

3 open issues
What changed in this PR

Adds an explicit branch-protection bypass option to the editor and sidebar for authorized users, addressing #3996 while reusing the existing merge flow.

Changes:

  • Fetches GitHub’s viewerCanMergeAsAdmin capability and propagates it through models and webviews.
  • Adds an unchecked bypass checkbox with eligibility guards and stale polling-response handling.
  • Adds regression coverage for capability refresh, consent, and eligibility.
File Description
webviews/​editorWebview/​test/​merge.test.tsx Tests bypass consent, guards, and polling behavior.
webviews/​editorWebview/​test/​builder/​pullRequest.ts Defaults bypass capability to false.
webviews/​components/​merge.tsx Adds bypass controls and snapshot-aware polling.
src/​test/​github/​pullRequestModel.test.ts Tests capability parsing, preservation, and refresh.
src/​test/​github/​activityBarViewProvider.test.ts Tests sidebar capability and mergeability updates.
src/​test/​builders/​graphql/​pullRequestBuilder.ts Adds the capability to GraphQL fixtures.
src/​github/​views.ts Exposes capability in the webview data contract.
src/​github/​utils.ts Parses GitHub’s bypass capability.
src/​github/​queriesShared.gql Fetches capability during mergeability checks.
src/​github/​queriesLimited.gql Adds capability to the limited PR fragment.
src/​github/​queriesExtra.gql Adds capability to the extended PR fragment.
src/​github/​queries.gql Adds capability to the standard PR fragment.
src/​github/​pullRequestOverview.ts Sends capability in editor initialization and updates.
src/​github/​pullRequestModel.ts Stores capability and emits change notifications.
src/​github/​interface.ts Extends PR and mergeability result types.
src/​github/​graphql.ts Extends GraphQL response types.
src/​github/​activityBarViewProvider.ts Propagates capability changes to the sidebar.

Review effort: Balanced

Comment thread webviews/components/merge.tsx Resolved
Comment thread src/github/pullRequestModel.ts Resolved
Comment thread webviews/components/merge.tsx Resolved
Copilot AI balanced review requested due to automatic review settings October 30, 2026 10:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

A pending mergeability callback can overwrite a newer capability revocation with stale data, incorrectly re-enabling the bypass UI.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Read current capability before publishing mergeability

src/​github/​pullRequestOverview.ts:774

If getMergeability() returns true while another deferred request is pending, a later capability revocation can publish false before this callback overwrites it with the stale true, re-enabling the bypass UI. During initial loading, a capability-only refresh does not advance _branchStatusSequence, so isBranchStatusCurrent does not prevent this. Read the capability from the current model when publishing instead of from the completed query result.

3 resolved since last review

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 30, 2026 10:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

A deferred update can overwrite a newer conflict status with stale mergeability data and incorrectly expose the bypass checkbox.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Prevent stale mergeability updates from restoring bypass eligibility

src/​github/​pullRequestOverview.ts:775

If mergeability changes from NotMergeable to Conflict while other initial deferred data is still loading, this update can overwrite the newer conflict status and expose the bypass checkbox. A mergeability-only refresh does not invalidate _branchStatusSequence before the initial comparison is published. Read mergeability from the current model alongside the capability so the deferred update cannot restore stale bypass eligibility.

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 30, 2026 10:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human assessment is needed because refresh failures can leave stale capability and mergeability state in the overview and sidebar, keeping a previously selected bypass action available after permission is revoked.

No open issues 🎉

Review effort: Balanced

@mstfkrdnz

Copy link
Copy Markdown

Thanks for this, it is exactly what we were missing. I built it locally on top of 0.174.0 and hit one gap worth sharing.

viewerCanMergeAsAdmin does not reflect repository ruleset bypass. Our protected branches use rulesets only (no classic branch protection). I am a bypass actor (bypass_mode: pull_request) on the ruleset that requires an approving review. On a PR where all required checks pass and only the review is missing:

  • github.com shows "Merge without waiting for requirements to be met (bypass rules)" and the merge works.
  • GraphQL returns mergeStateStatus: BLOCKED and viewerCanMergeAsAdmin: false for the same user and PR.
  • REST GET /repos/{owner}/{repo}/rulesets/{id} returns current_user_can_bypass: "pull_requests_only" for that ruleset (and "never" for the one holding the required status checks).

So with this PR as is, the checkbox never appears for ruleset-based repos. Locally I added a fallback that only runs when the PR is BLOCKED/BEHIND and viewerCanMergeAsAdmin is not true:

// GitHubRepository
async canViewerBypassBranchRules(branch: string): Promise<boolean> {
	// cached per branch for 5 minutes
	const { octokit, remote } = await this.ensure();
	const owner = remote.owner;
	const repo = remote.repositoryName;
	const rules = await octokit.call(octokit.api.repos.getBranchRules, { owner, repo, branch, per_page: 100 });
	const ids = [...new Set(rules.data.map(rule => rule.ruleset_id).filter((id): id is number => typeof id === 'number'))];
	for (const ruleset_id of ids) {
		const ruleset = await octokit.call(octokit.api.repos.getRepoRuleset, { owner, repo, ruleset_id });
		const can = ruleset.data.current_user_can_bypass;
		if (can === 'always' || can === 'pull_requests_only' || can === 'exempt') {
			return true;
		}
	}
	return false;
}

// PullRequestModel.getMergeability, after the PullRequestMergeability query
const fetched = data.repository?.pullRequest;
if (fetched && fetched.viewerCanMergeAsAdmin !== true
	&& (fetched.mergeStateStatus === 'BLOCKED' || fetched.mergeStateStatus === 'BEHIND')
	&& this.base?.ref) {
	fetched.viewerCanMergeAsAdmin = await this.githubRepository.canViewerBypassBranchRules(this.base.ref);
}

With that the checkbox shows up on our blocked PR, matching the web UI. Two caveats:

  • It is coarser than the web UI: it answers "can the viewer bypass any ruleset on this branch", not "can the viewer bypass the specific unmet rules". If the unmet rule is one the viewer cannot bypass (e.g. required checks with never), the checkbox still appears and GitHub rejects the merge. Authorization stays server-side either way.
  • It costs 1 + N REST calls (N = rulesets on the branch), only for blocked/behind PRs. A short cache keeps the 3s mergeability polling from repeating them.

Happy to open this as a follow-up PR if that is easier to review separately.

Also a small visual note: the checkbox has no styling, so in the overview it sits flush against the bottom border of the status box with the label touching the input.

Copilot AI balanced review requested due to automatic review settings November 3, 2026 17:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human assessment is needed because branch changes and failed or delayed refreshes can leave stale mergeability and bypass permissions reflected in the overview controls.

No open issues 🎉

Review effort: Balanced

@mstfkrdnz

Copy link
Copy Markdown

Thanks for picking this up so quickly. I rebuilt e7ad01a on top of 0.174.0 and tried it on the same ruleset-only repo: on a PR blocked only by the required review, the "Bypass branch rules and merge" checkbox now appears in the overview, and the new layout reads well.

The enforcement === 'active' check and the includes_parents lookup also cover two cases my local version missed.

Copilot AI balanced review requested due to automatic review settings November 4, 2026 15:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved refresh-state issues can leave stale bypass permissions and merge actions visible or incorrectly remove valid ruleset bypass access.

2 open issues

Review effort: Balanced

Comment thread src/github/pullRequestOverview.ts Resolved
Comment thread src/github/utils.ts Resolved
Copilot AI balanced review requested due to automatic review settings November 4, 2026 16:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Branch retargeting can leave bypass available because stale permission grants and consent for the previous target are not reliably cleared.

4 open issues
2 resolved since last review

Review effort: Balanced

Comment thread src/github/pullRequestModel.ts Resolved
Comment thread src/github/pullRequestOverview.ts Resolved
Comment thread src/github/githubRepository.ts Resolved
Comment thread src/github/pullRequestModel.ts Resolved
Copilot AI balanced review requested due to automatic review settings November 4, 2026 17:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Retargeting a PR to a branch requiring a merge queue leaves stale eligibility state, allowing bypass and direct merge actions until the new queue lookup completes.

1 open issue
4 resolved since last review

Review effort: Balanced

Comment thread src/github/pullRequestOverview.ts Resolved
Copilot AI balanced review requested due to automatic review settings November 4, 2026 17:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Clearing stack during reload bypasses the stack guard and temporarily exposes single-PR merge controls for a stacked PR, risking an accidental merge through the wrong flow.

1 open issue
1 resolved since last review

Review effort: Balanced

Comment thread src/github/pullRequestOverview.ts Resolved
Copilot AI balanced review requested due to automatic review settings November 4, 2026 17:48

Copilot AI commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Install dependencies

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human assessment is needed because retargeting can retain stale branch state and bypass consent, while eligibility refreshes can expose direct merging before queue eligibility is known.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

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.

Allow bypass branch protection in the extension

3 participants