Skip to content

fix(copilot): explain review request denials instead of forwarding a bare 404 - #3119

Merged
SamMorrowDrums merged 2 commits into
github:mainfrom
dylanpulver:fix/copilot-review-cross-fork
Aug 19, 2026
Merged

fix(copilot): explain review request denials instead of forwarding a bare 404#3119
SamMorrowDrums merged 2 commits into
github:mainfrom
dylanpulver:fix/copilot-review-cross-fork

Conversation

@dylanpulver

@dylanpulver dylanpulver commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

request_copilot_review forwarded GitHub's 404 verbatim, so a caller who simply lacked write access on the target repository got a tool result that read as if the repository or the pull request did not exist. The tool now identifies which cause applies and says so.

Why

Fixes #3027

The reporter authored a cross-fork pull request into microsoft/vscode, requested a Copilot review from the GitHub website successfully, then got 404 Not Found from the same request through this tool. Nothing in the tool result explained the difference, so the report reasonably concluded the tool was calling the wrong endpoint.

Root cause. POST /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers requires write access to the repository, and authoring the pull request does not grant it (pull request reviews reference). The endpoint documents 403 and 422 as its denial responses (review requests REST reference), but what it actually returns is a 404 carrying no permission signal. Confirmed against the live API with a token holding full repo scope on a repository where permissions.push is false:

POST /repos/github/github-mcp-server/pulls/3038/requested_reviewers
HTTP/2.0 404 Not Found
X-Oauth-Scopes: gist, read:org, repo, workflow

The reviewer login was deliberately invalid so the call could not succeed, which also shows the permission check runs before reviewer validation. Token scope is not the problem, repository write access is.

There is no fallback to add. GraphQL exposes only requestReviews and requestReviewsByLogin, and both enforce the same gate. Calling requestReviewsByLogin as the same non-write user returns FORBIDDEN: ... does not have the correct permissions to execute RequestReviewsByLogin, so GraphQL refuses the same actor for the same reason and is only more honest about it. Whatever path the website uses to offer Copilot to a fork author is not exposed as a public API endpoint, so the fix goes to the error surface.

What changed

  • copilotReviewErrMsg in pkg/github/copilot.go enriches the failure message when the response is 403 or 404. It reads the repository once, on the failure path only, and reports which cause applies: no write access (pointing at the pull request page on the website, which is what the reporter found working), a repository that cannot be read at all, or a repository the caller can write to (pointing at the pull request number and Copilot availability instead).
  • Rate limiting also surfaces as 403. *github.RateLimitError and *github.AbuseRateLimitError short-circuit to the original message, so a rate-limited caller is not told they lack write access and does not pay a second call that would be refused for the same reason.
  • The shape follows dependabotErrMsg in pkg/github/dependabot.go, the existing helper in this repo that appends a permission hint on 403 or 404. Any other status keeps the original message, so unrelated failures are not mislabelled, and the success path is untouched.
  • Scoped to request_copilot_review on purpose. The other callers of RequestReviewers hit the same platform behavior, but the remedy text here is specific to Copilot code review, so a shared helper would not carry over cleanly.

MCP impact

  • No tool or API changes
  • Tool schema or behavior changed
  • New tool added

The tool schema, name, description and success result are untouched. Only the text of an existing error result changes, so the toolsnap is unchanged.

Prompts tested (tool changes only)

No tool schema change, so no new prompts. The behavior was exercised through the unit tests below and the underlying API behavior was verified directly against api.github.com as shown above.

Security / limits

  • No security or limits impact
  • Auth / permissions considered
  • Data exposure, filtering, or token/size limits considered

The added repository read uses the caller's own client and runs only after a request has already failed, so it grants no access the caller did not have and adds no call to the success path. The message names only the owner and repository the caller supplied, and reports write access as a boolean rather than echoing any response body.

Tool renaming

  • I am renaming tools as part of this PR (e.g. a part of a consolidation effort)
    • I have added the new tool aliases in deprecated_tool_aliases.go
  • I am not renaming tools as part of this PR

Lint & tests

  • Linted locally with ./script/lint
  • Tested locally with ./script/test

./script/lint reports 0 issues and ./script/test passes across every package. ./script/generate-docs produces no diff, confirming the tool surface is unchanged. Six cases were added to Test_RequestCopilotReview:

  • pull request author without write access, the case from the issue: 404 plus a repository whose permissions.push is false, asserting the message names the missing write access.
  • forbidden is explained the same way as not found, so the documented 403 gets the same treatment as the 404 that actually shows up.
  • write access present points at the pull request instead, asserting the message names pull request 999 rather than blaming permissions.
  • unreadable repository, where the repository read also fails.
  • server error is not explained as a permission problem, asserting a 500 keeps the original message and gains no permission text.
  • rate limited forbidden is not explained as a permission problem, asserting a rate-limited 403 keeps the rate limit message and never reads the repository.

Docs

  • Not needed
  • Updated (README / docs / examples)

The tool description and generated docs are unchanged.

@dylanpulver
dylanpulver requested a review from a team as a code owner August 19, 2026 14:06
dylanpulver and others added 2 commits August 19, 2026 16:51
…bare 404

The review request endpoint requires write access to the repository, and
GitHub refuses a caller without it with 404 Not Found rather than a
permission error. Authoring the pull request does not grant that access,
so a fork contributor can be offered a Copilot review by the web UI and
still be refused by request_copilot_review, with nothing in the tool
result to say why.

On 403 or 404 the tool now reads the repository once so it can name the
cause. A caller without write access is told so directly and pointed at
the web UI. When the repository cannot be read at all, or when write
access is present, the message says so and points at the likelier
cause.
A 403 carrying X-RateLimit-Remaining: 0, or a secondary rate limit
documentation URL, reached copilotReviewErrMsg as a rate limit error. It
was explained as a missing repository or missing write access, and the
repository read it triggered was refused for the same reason, so the
caller paid an extra call to be told the wrong thing.

Return the base message for both rate limit error types so the rate
limit text stands on its own, and trim the helper and its tests to the
comments the code cannot state.

Co-authored-by: Dylan Pulver <dylanpulver@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@SamMorrowDrums
SamMorrowDrums force-pushed the fix/copilot-review-cross-fork branch from e9a8d45 to 631703f Compare August 19, 2026 14:53
@SamMorrowDrums
SamMorrowDrums merged commit 24dc8b0 into github:main Aug 19, 2026
13 checks passed

@SamMorrowDrums SamMorrowDrums left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sanity-checked the failure-path behavior, rate-limit handling, tests, and comment cleanup. CI is green and the branch is current.

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.

request_copilot_review returns 404 for PR author on cross-fork PR while GitHub UI succeeds

2 participants