From 196251bbf15401703b055e39855ba7019e0d5fa6 Mon Sep 17 00:00:00 2001 From: Bryan Morgan Date: Wed, 11 Feb 2026 20:02:25 -0500 Subject: [PATCH 1/4] fix(workflows): improve maintainer detection in stale PR closer Updated the PR closer to check multiple maintainer teams and verify Googler status. This prevents incorrectly closing PRs from maintainers who may not be in the primary maintainers team but are Googlers or members of other relevant teams. --- .../gemini-scheduled-stale-pr-closer.yml | 65 ++++++++++++++----- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-pr-closer.yml b/.github/workflows/gemini-scheduled-stale-pr-closer.yml index 90d7417b055..a69075d1581 100644 --- a/.github/workflows/gemini-scheduled-stale-pr-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-pr-closer.yml @@ -43,23 +43,56 @@ jobs: // 1. Fetch maintainers for verification let maintainerLogins = new Set(); - let teamFetchSucceeded = false; - try { - const members = await github.paginate(github.rest.teams.listMembersInOrg, { - org: context.repo.owner, - team_slug: 'gemini-cli-maintainers' - }); - maintainerLogins = new Set(members.map(m => m.login.toLowerCase())); - teamFetchSucceeded = true; - core.info(`Successfully fetched ${maintainerLogins.size} team members from gemini-cli-maintainers`); - } catch (e) { - core.warning(`Failed to fetch team members from gemini-cli-maintainers: ${e.message}. Falling back to author_association only.`); + const teams = ['gemini-cli-maintainers', 'gemini-cli-askmode-approvers', 'gemini-cli-docs']; + + for (const team_slug of teams) { + try { + const members = await github.paginate(github.rest.teams.listMembersInOrg, { + org: context.repo.owner, + team_slug: team_slug + }); + for (const m of members) maintainerLogins.add(m.login.toLowerCase()); + core.info(`Successfully fetched ${members.length} team members from ${team_slug}`); + } catch (e) { + core.warning(`Failed to fetch team members from ${team_slug}: ${e.message}`); + } } - const isMaintainer = (login, assoc) => { + const isGooglerCache = new Map(); + const isGoogler = async (login) => { + if (isGooglerCache.has(login)) return isGooglerCache.get(login); + + try { + // Check membership in 'googlers' or 'google' orgs + const orgs = ['googlers', 'google']; + for (const org of orgs) { + try { + await github.rest.orgs.checkMembershipForUser({ + org: org, + username: login + }); + core.info(`User ${login} is a member of ${org} organization.`); + isGooglerCache.set(login, true); + return true; + } catch (e) { + // 404 just means they aren't a member, which is fine + if (e.status !== 404) throw e; + } + } + } catch (e) { + core.warning(`Failed to check org membership for ${login}: ${e.message}`); + } + + isGooglerCache.set(login, false); + return false; + }; + + const isMaintainer = async (login, assoc) => { const isTeamMember = maintainerLogins.has(login.toLowerCase()); const isRepoMaintainer = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(assoc); - return isTeamMember || isRepoMaintainer; + if (isTeamMember || isRepoMaintainer) return true; + + return await isGoogler(login); }; // 2. Determine which PRs to check @@ -81,7 +114,7 @@ jobs: } for (const pr of prs) { - const maintainerPr = isMaintainer(pr.user.login, pr.author_association); + const maintainerPr = await isMaintainer(pr.user.login, pr.author_association); const isBot = pr.user.type === 'Bot' || pr.user.login.endsWith('[bot]'); // Detection Logic for Linked Issues @@ -175,7 +208,7 @@ jobs: pull_number: pr.number }); for (const r of reviews) { - if (isMaintainer(r.user.login, r.author_association)) { + if (await isMaintainer(r.user.login, r.author_association)) { const d = new Date(r.submitted_at || r.updated_at); if (d > lastActivity) lastActivity = d; } @@ -186,7 +219,7 @@ jobs: issue_number: pr.number }); for (const c of comments) { - if (isMaintainer(c.user.login, c.author_association)) { + if (await isMaintainer(c.user.login, c.author_association)) { const d = new Date(c.updated_at); if (d > lastActivity) lastActivity = d; } From 9f59a5055667b3083096f5d988dd89f1876f9e76 Mon Sep 17 00:00:00 2001 From: Bryan Morgan Date: Wed, 11 Feb 2026 20:12:49 -0500 Subject: [PATCH 2/4] fix(workflows): update maintainer check in PR guidelines notifier Added Googler status verification to the PR guidelines notifier to prevent sending unnecessary notifications to maintainers who are Googlers. --- .../pr-contribution-guidelines-notifier.yml | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-contribution-guidelines-notifier.yml b/.github/workflows/pr-contribution-guidelines-notifier.yml index fdabd20f3d2..c015ee57bbc 100644 --- a/.github/workflows/pr-contribution-guidelines-notifier.yml +++ b/.github/workflows/pr-contribution-guidelines-notifier.yml @@ -35,9 +35,31 @@ jobs: const pr_number = context.payload.pull_request.number; // 1. Check if the PR author is a maintainer + const isGoogler = async (login) => { + try { + const orgs = ['googlers', 'google']; + for (const org of orgs) { + try { + await github.rest.orgs.checkMembershipForUser({ + org: org, + username: login + }); + return true; + } catch (e) { + if (e.status !== 404) throw e; + } + } + } catch (e) { + core.warning(`Failed to check org membership for ${login}: ${e.message}`); + } + return false; + }; + const authorAssociation = context.payload.pull_request.author_association; - if (['OWNER', 'MEMBER', 'COLLABORATOR'].includes(authorAssociation)) { - core.info(`${username} is a maintainer (Association: ${authorAssociation}). No notification needed.`); + const isRepoMaintainer = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(authorAssociation); + + if (isRepoMaintainer || await isGoogler(username)) { + core.info(`${username} is a maintainer or Googler. No notification needed.`); return; } From 091fab988d93d0616c01d09afff6d29acdd43d1b Mon Sep 17 00:00:00 2001 From: Bryan Morgan Date: Wed, 11 Feb 2026 20:15:31 -0500 Subject: [PATCH 3/4] style: remove trailing whitespace in stale PR closer workflow --- .github/workflows/gemini-scheduled-stale-pr-closer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gemini-scheduled-stale-pr-closer.yml b/.github/workflows/gemini-scheduled-stale-pr-closer.yml index a69075d1581..9aae56d2832 100644 --- a/.github/workflows/gemini-scheduled-stale-pr-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-pr-closer.yml @@ -44,7 +44,7 @@ jobs: // 1. Fetch maintainers for verification let maintainerLogins = new Set(); const teams = ['gemini-cli-maintainers', 'gemini-cli-askmode-approvers', 'gemini-cli-docs']; - + for (const team_slug of teams) { try { const members = await github.paginate(github.rest.teams.listMembersInOrg, { From 1ccc5291990d8f24baba6457a51decf9217e71c3 Mon Sep 17 00:00:00 2001 From: Bryan Morgan Date: Wed, 11 Feb 2026 20:27:14 -0500 Subject: [PATCH 4/4] style: fix trailing-spaces yamllint errors --- .github/workflows/gemini-scheduled-stale-pr-closer.yml | 6 +++--- .github/workflows/pr-contribution-guidelines-notifier.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-pr-closer.yml b/.github/workflows/gemini-scheduled-stale-pr-closer.yml index 9aae56d2832..bd7fd0ddc9a 100644 --- a/.github/workflows/gemini-scheduled-stale-pr-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-pr-closer.yml @@ -61,7 +61,7 @@ jobs: const isGooglerCache = new Map(); const isGoogler = async (login) => { if (isGooglerCache.has(login)) return isGooglerCache.get(login); - + try { // Check membership in 'googlers' or 'google' orgs const orgs = ['googlers', 'google']; @@ -82,7 +82,7 @@ jobs: } catch (e) { core.warning(`Failed to check org membership for ${login}: ${e.message}`); } - + isGooglerCache.set(login, false); return false; }; @@ -91,7 +91,7 @@ jobs: const isTeamMember = maintainerLogins.has(login.toLowerCase()); const isRepoMaintainer = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(assoc); if (isTeamMember || isRepoMaintainer) return true; - + return await isGoogler(login); }; diff --git a/.github/workflows/pr-contribution-guidelines-notifier.yml b/.github/workflows/pr-contribution-guidelines-notifier.yml index c015ee57bbc..26585203710 100644 --- a/.github/workflows/pr-contribution-guidelines-notifier.yml +++ b/.github/workflows/pr-contribution-guidelines-notifier.yml @@ -57,7 +57,7 @@ jobs: const authorAssociation = context.payload.pull_request.author_association; const isRepoMaintainer = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(authorAssociation); - + if (isRepoMaintainer || await isGoogler(username)) { core.info(`${username} is a maintainer or Googler. No notification needed.`); return;