fix(copilot): explain review request denials instead of forwarding a bare 404 - #3119
Merged
SamMorrowDrums merged 2 commits intoAug 19, 2026
Merged
Conversation
…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
force-pushed
the
fix/copilot-review-cross-fork
branch
from
August 19, 2026 14:53
e9a8d45 to
631703f
Compare
SamMorrowDrums
approved these changes
Aug 19, 2026
SamMorrowDrums
approved these changes
Aug 19, 2026
SamMorrowDrums
left a comment
Collaborator
There was a problem hiding this comment.
Sanity-checked the failure-path behavior, rate-limit handling, tests, and comment cleanup. CI is green and the branch is current.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
request_copilot_reviewforwarded 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 got404 Not Foundfrom 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_reviewersrequires 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 fullreposcope on a repository wherepermissions.pushisfalse: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
requestReviewsandrequestReviewsByLogin, and both enforce the same gate. CallingrequestReviewsByLoginas the same non-write user returnsFORBIDDEN: ... 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
copilotReviewErrMsginpkg/github/copilot.goenriches 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).*github.RateLimitErrorand*github.AbuseRateLimitErrorshort-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.dependabotErrMsginpkg/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.request_copilot_reviewon purpose. The other callers ofRequestReviewershit 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
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.comas shown above.Security / limits
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
deprecated_tool_aliases.goLint & tests
./script/lint./script/test./script/lintreports 0 issues and./script/testpasses across every package../script/generate-docsproduces no diff, confirming the tool surface is unchanged. Six cases were added toTest_RequestCopilotReview:pull request author without write access, the case from the issue: 404 plus a repository whosepermissions.pushis 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
The tool description and generated docs are unchanged.