Skip to content

fix(ng-dev): verify validated head SHA before merging a pull request - #3911

Open
herdiyana256 wants to merge 1 commit into
angular:mainfrom
herdiyana256:fix/pr-merge-validate-head-sha
Open

fix(ng-dev): verify validated head SHA before merging a pull request#3911
herdiyana256 wants to merge 1 commit into
angular:mainfrom
herdiyana256:fix/pr-merge-validate-head-sha

Conversation

@herdiyana256

Copy link
Copy Markdown
Contributor

ng-dev pr merge validates a pull request (approvals, CI status, target labels) against the head commit the GitHub API reports when the PR is loaded, and stores it as pullRequest.headSha. That value was never checked again, so both merge strategies operate on the live pull request head instead of the commit that was validated:

  • MergeStrategy.prepare fetches the mutable pull/<number>/head ref into merge_pr_head, and the autosquash strategy rebases and cherry-picks from there.
  • GithubApiMergeStrategy calls pulls.merge without a sha, so GitHub merges whatever the head currently is.

Between the validation read and the fetch (or the API call) the pull request author can push a new commit. The merge then lands that commit even though it was never reviewed or run through CI, a time-of-check/time-of-use gap.

prepare now resolves the fetched head and fails closed unless it equals the validated headSha, and the API merge strategy passes headSha to pulls.merge so the server rejects the merge with a 409 if the head has moved since validation. A unit test covers both the mismatch (rejected) and match (allowed) cases.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces validation to ensure that a pull request's head commit has not changed between validation and merging, preventing the landing of unreviewed commits. It adds a new MismatchedPullRequestHeadShaFatalError and checks the fetched head SHA against the expected SHA in MergeStrategy#prepare, as well as pinning the API merge to the validated head SHA. Feedback suggests updating the error handling in GithubApiMergeStrategy to gracefully handle HTTP 409 errors from GitHub when the head SHA mismatches during the merge call.

Comment on lines +70 to +74
// Pin the merge to the head commit that was validated. `pulls.merge` otherwise merges
// whatever the pull request head currently is, so a commit pushed after validation but
// before this call would be merged without review or CI. With `sha` set, GitHub rejects
// the merge (HTTP 409) if the head has moved since it was validated.
sha: headSha,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Since you are now passing sha: headSha to pulls.merge, GitHub will reject the merge with an HTTP 409 status code if the head has moved since validation.

Currently, the catch block below (around line 139) only handles 403 and 404 errors:

      if (isGithubApiError(e) && (e.status === 403 || e.status === 404)) {
        throw new FatalMergeToolError('Insufficient Github API permissions to merge pull request.');
      }

If a 409 occurs, it will be rethrown as a raw GitHub API error, which results in an unhelpful error message and stack trace for the user.

Consider updating the catch block to also handle 409 and throw a user-friendly error (e.g., MismatchedPullRequestHeadShaFatalError or a descriptive FatalMergeToolError).

The merge tooling validates a pull request (approvals, CI status, target
labels) against the head commit the GitHub API reports when the PR is loaded
and stores it as `pullRequest.headSha`, but the value was never enforced. Both
merge strategies then operate on the live PR head instead: `MergeStrategy.prepare`
fetches the mutable `pull/<number>/head` ref into a local branch and the merge
proceeds from there, and `GithubApiMergeStrategy` calls `pulls.merge` without a
`sha`. A commit pushed to the pull request after validation but before the fetch
or API call is therefore merged without ever being reviewed or checked.

`prepare` now resolves the fetched head and fails closed unless it equals the
validated `headSha`, and the API merge strategy passes `headSha` to `pulls.merge`
so the server rejects the merge if the head has moved.
@herdiyana256
herdiyana256 force-pushed the fix/pr-merge-validate-head-sha branch from a3b54f5 to 0f690ac Compare August 8, 2026 08:04
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.

1 participant