From 24fb7657cd9fd0460ac57ed33a767227073bac95 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:47:24 -0700 Subject: [PATCH] Exclude PR author from reviewer candidates in SuggestedReviewerActors Add author { login } to the SuggestedReviewerActors GraphQL query and pre-seed the seen map with the author login so they are excluded from all sources (suggestions, collaborators, teams). Previously the author was only skipped via the isAuthor flag in the suggestions loop but could still appear as a collaborator. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- api/queries_pr_review.go | 7 +++++++ api/queries_pr_test.go | 38 +++++++++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/api/queries_pr_review.go b/api/queries_pr_review.go index f6ef2a74e..3d0132cbd 100644 --- a/api/queries_pr_review.go +++ b/api/queries_pr_review.go @@ -413,6 +413,9 @@ func SuggestedReviewerActors(client *Client, repo ghrepo.Interface, prID string, type responseData struct { Node struct { PullRequest struct { + Author struct { + Login string + } SuggestedActors struct { Nodes []struct { IsAuthor bool @@ -472,7 +475,11 @@ func SuggestedReviewerActors(client *Client, repo ghrepo.Interface, prID string, // Build candidates using cascading quota logic: // Each source has a base quota of 5, plus any unfilled quota from previous sources. // This ensures we show up to 15 total candidates, filling gaps when earlier sources have fewer. + // Pre-seed seen with the PR author since you cannot review your own PR. seen := make(map[string]bool) + if authorLogin := result.Node.PullRequest.Author.Login; authorLogin != "" { + seen[authorLogin] = true + } var candidates []ReviewerCandidate const baseQuota = 5 diff --git a/api/queries_pr_test.go b/api/queries_pr_test.go index 7b7727f3e..8fff120bb 100644 --- a/api/queries_pr_test.go +++ b/api/queries_pr_test.go @@ -160,7 +160,7 @@ func mockReviewerResponse(suggestions, collabs, teams, totalCollabs, totalTeams return fmt.Sprintf(`{ "data": { - "node": {"suggestedReviewerActors": {"nodes": [%s]}}, + "node": {"author": {"login": "testauthor"}, "suggestedReviewerActors": {"nodes": [%s]}}, "repository": { "collaborators": {"nodes": [%s]}, "collaboratorsTotalCount": {"totalCount": %d} @@ -235,7 +235,7 @@ func TestSuggestedReviewerActors(t *testing.T) { httpmock.GraphQL(`query SuggestedReviewerActors\b`), httpmock.StringResponse(`{ "data": { - "node": {"suggestedReviewerActors": {"nodes": [ + "node": {"author": {"login": "testauthor"}, "suggestedReviewerActors": {"nodes": [ {"isAuthor": true, "reviewer": {"__typename": "User", "login": "author", "name": "Author"}}, {"isAuthor": false, "reviewer": {"__typename": "User", "login": "s1", "name": "S1"}}, {"isAuthor": false, "reviewer": {"__typename": "User", "login": "s2", "name": "S2"}} @@ -255,6 +255,34 @@ func TestSuggestedReviewerActors(t *testing.T) { expectedLogins: []string{"s1", "s2", "c1", "OWNER/team1"}, expectedMore: 8, }, + { + name: "author excluded from collaborators", + httpStubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.GraphQL(`query SuggestedReviewerActors\b`), + httpmock.StringResponse(`{ + "data": { + "node": {"author": {"login": "theauthor"}, "suggestedReviewerActors": {"nodes": [ + {"isAuthor": false, "reviewer": {"__typename": "User", "login": "s1", "name": "S1"}} + ]}}, + "repository": { + "collaborators": {"nodes": [ + {"login": "theauthor", "name": "The Author"}, + {"login": "c1", "name": "C1"} + ]}, + "collaboratorsTotalCount": {"totalCount": 5} + }, + "organization": { + "teams": {"nodes": []}, + "teamsTotalCount": {"totalCount": 0} + } + } + }`)) + }, + expectedCount: 2, + expectedLogins: []string{"s1", "c1"}, + expectedMore: 5, + }, { name: "deduplication across sources", httpStubs: func(reg *httpmock.Registry) { @@ -263,7 +291,7 @@ func TestSuggestedReviewerActors(t *testing.T) { httpmock.GraphQL(`query SuggestedReviewerActors\b`), httpmock.StringResponse(`{ "data": { - "node": {"suggestedReviewerActors": {"nodes": [ + "node": {"author": {"login": "testauthor"}, "suggestedReviewerActors": {"nodes": [ {"isAuthor": false, "reviewer": {"__typename": "User", "login": "shareduser", "name": "Shared"}} ]}}, "repository": { @@ -291,7 +319,7 @@ func TestSuggestedReviewerActors(t *testing.T) { httpmock.GraphQL(`query SuggestedReviewerActors\b`), httpmock.StringResponse(`{ "data": { - "node": {"suggestedReviewerActors": {"nodes": [ + "node": {"author": {"login": "testauthor"}, "suggestedReviewerActors": {"nodes": [ {"isAuthor": false, "reviewer": {"__typename": "User", "login": "s1", "name": "S1"}} ]}}, "repository": { @@ -314,7 +342,7 @@ func TestSuggestedReviewerActors(t *testing.T) { httpmock.GraphQL(`query SuggestedReviewerActors\b`), httpmock.StringResponse(`{ "data": { - "node": {"suggestedReviewerActors": {"nodes": [ + "node": {"author": {"login": "testauthor"}, "suggestedReviewerActors": {"nodes": [ {"isAuthor": false, "reviewer": {"__typename": "Bot", "login": "copilot-pull-request-reviewer"}}, {"isAuthor": false, "reviewer": {"__typename": "User", "login": "s1", "name": "S1"}} ]}},