11name : Auto Assign Reviewers
22
33on :
4+ # pull_request_target is required to grant write permissions for reviewer assignment on fork PRs.
5+ # The workflow only runs github-script and does not check out or execute untrusted code.
6+ # zizmor: ignore[dangerous-triggers]
47 pull_request_target :
5- types : [opened, ready_for_review, reopened]
8+ types : [opened, ready_for_review, reopened, synchronize ]
69
710permissions :
811 pull-requests : write
@@ -17,54 +20,84 @@ jobs:
1720 continue-on-error : true
1821 uses : actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
1922 with :
20- github-token : ${{ secrets.GOOGLER_CHECK_TOKEN || secrets. GITHUB_TOKEN }}
23+ github-token : ${{ secrets.GITHUB_TOKEN }}
2124 script : |
2225 const author = context.payload.pull_request.user.login;
23-
24- const ALLOWED_BOTS = new Set([
25- 'gcf-owl-bot[bot]',
26- 'gcf-owl-bot',
27- 'release-please[bot]',
28- 'release-please',
29- 'dependabot[bot]',
30- 'dependabot',
31- 'renovate-bot',
32- 'renovate',
33- 'yoshi-code-bot'
34- ]);
35-
36- let isGoogler = ALLOWED_BOTS.has(author);
37-
38- if (isGoogler) {
39- console.log(`${author} is a trusted bot. Treating as Googler.`);
40- } else {
41- const orgs = ['googlers', 'GoogleCloudPlatform', 'googleapis'];
42- for (const org of orgs) {
26+
27+ // Release PRs should remain the responsibility of the oncall,
28+ // so we route them only to the default team rather than individual team members.
29+ if (author === 'release-please[bot]' || author === 'release-please') {
30+ console.log(`PR opened by ${author}. Ensuring cloud-sdk-nodejs-team is assigned as a reviewer.`);
31+ const requestedTeams = context.payload.pull_request.requested_teams || [];
32+ const hasDefaultTeam = requestedTeams.some(team => team.slug === 'cloud-sdk-nodejs-team');
33+ if (!hasDefaultTeam) {
4334 try {
44- const res = await github.rest.orgs.checkMembershipForUser({
45- org: org,
46- username: author,
35+ await github.rest.pulls.requestReviewers({
36+ owner: context.repo.owner,
37+ repo: context.repo.repo,
38+ pull_number: context.payload.pull_request.number,
39+ team_reviewers: ['cloud-sdk-nodejs-team'],
4740 });
48- if (res.status === 204) {
49- isGoogler = true;
50- console.log(`${author} is a member of '${org}' organization.`);
51- break;
52- }
53- } catch (error) {
54- if (error.status === 404) {
55- console.log(`${author} is NOT a member of '${org}' organization.`);
56- } else {
57- console.warn(`Could not check membership in '${org}' organization: Status ${error.status}.`);
58- }
41+ } catch (err) {
42+ console.error("Failed to assign default cloud-sdk-nodejs-team reviewer:", err.message || err);
5943 }
6044 }
45+ return;
46+ }
47+
48+ const requestedReviewers = context.payload.pull_request.requested_reviewers || [];
49+ const requestedTeams = context.payload.pull_request.requested_teams || [];
50+
51+ const hasDefaultTeam = requestedTeams.some(team => team.slug === 'cloud-sdk-nodejs-team');
52+ const hasRouteSpecificTeam = requestedTeams.some(team => team.slug !== 'cloud-sdk-nodejs-team');
53+
54+ // Check if PR has already been reviewed
55+ let hasReviews = false;
56+ try {
57+ const { data: reviews } = await github.rest.pulls.listReviews({
58+ owner: context.repo.owner,
59+ repo: context.repo.repo,
60+ pull_number: context.payload.pull_request.number,
61+ });
62+ if (reviews.length > 0) {
63+ console.log("PR already has reviews.");
64+ hasReviews = true;
65+ }
66+ } catch (err) {
67+ console.warn("Failed to check PR reviews:", err.message || err);
6168 }
6269
63- if (!isGoogler) {
64- console.log("PR not opened by a Googler. Skipping auto-assignment.");
70+ let shouldExit = false;
71+ if (requestedReviewers.length > 0) {
72+ console.log(`PR already has requested reviewers: ${requestedReviewers.map(r => r.login).join(', ')}.`);
73+ shouldExit = true;
74+ } else if (hasRouteSpecificTeam) {
75+ console.log(`PR already has route-specific team reviewer requested: ${requestedTeams.map(t => t.slug).join(', ')}.`);
76+ shouldExit = true;
77+ } else if (hasReviews) {
78+ shouldExit = true;
79+ }
80+
81+ if (shouldExit) {
82+ if (hasDefaultTeam) {
83+ console.log("PR already has individual, route-specific reviewers, or reviews, but default cloud-sdk-nodejs-team is still requested. Removing it...");
84+ try {
85+ await github.rest.pulls.removeRequestedReviewers({
86+ owner: context.repo.owner,
87+ repo: context.repo.repo,
88+ pull_number: context.payload.pull_request.number,
89+ reviewers: [],
90+ team_reviewers: ['cloud-sdk-nodejs-team'],
91+ });
92+ } catch (err) {
93+ console.warn("Failed to remove default cloud-sdk-nodejs-team reviewer:", err.message || err);
94+ }
95+ }
96+ console.log("Skipping auto-assignment.");
6597 return;
6698 }
6799
100+
68101 // 2. Get list of files modified in the PR
69102 const { data: files } = await github.rest.pulls.listFiles({
70103 owner: context.repo.owner,
@@ -213,6 +246,7 @@ jobs:
213246 owner: context.repo.owner,
214247 repo: context.repo.repo,
215248 pull_number: context.payload.pull_request.number,
249+ reviewers: [],
216250 team_reviewers: ['cloud-sdk-nodejs-team'],
217251 });
218252 } catch (err) {
0 commit comments