From e2ab11763a945504988d4e246181b68a1db07197 Mon Sep 17 00:00:00 2001 From: dennis zhuang Date: Mon, 10 Aug 2026 21:14:56 +0800 Subject: [PATCH] fix(ci): identify team members by repository permission (#8822) * fix(ci): identify team members by repository permission `author_association` is computed from what the caller can see, so GITHUB_TOKEN reports a private organization member as CONTRIBUTOR. Only 5 of GreptimeTeam's members have public membership, so the open-pull-request check skipped almost everyone it was written for. Use the repository permission of the author instead, which is viewer-independent. On error, apply the limit rather than skipping, so a token that cannot read permissions cannot silently disable the check again. Signed-off-by: Dennis Zhuang * fix(ci): do not log repository permission levels Job logs are public. Resolving the author's permission is fine; printing the level is not. Signed-off-by: Dennis Zhuang * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Signed-off-by: Dennis Zhuang Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/scripts/pr-open-limit.js | 37 ++++++++++++++++++++++------- .github/workflows/pr-open-limit.yml | 1 - 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/.github/scripts/pr-open-limit.js b/.github/scripts/pr-open-limit.js index 1ba908b745..dd1650b0d6 100644 --- a/.github/scripts/pr-open-limit.js +++ b/.github/scripts/pr-open-limit.js @@ -9,9 +9,8 @@ import { appendFileSync } from "node:fs"; const MARKER = ""; // GITHUB_TOKEN can only edit comments authored by the Actions bot itself. const COMMENT_AUTHOR = "github-actions[bot]"; -// Associations that identify an organization member. Outside collaborators and -// community contributors are out of scope. -const MEMBER_ASSOCIATIONS = ["OWNER", "MEMBER"]; +// Repository permissions that mark someone as part of the team. +const TEAM_PERMISSIONS = ["admin", "maintain", "write"]; // Keep the comment readable for authors who are far over the limit. const MAX_LISTED_PRS = 10; @@ -22,6 +21,29 @@ function summary(text) { console.log(text); } +// `author_association` cannot be used here: it is computed from what the caller +// is allowed to see, so GITHUB_TOKEN reports a private organization member as +// CONTRIBUTOR. Repository permission is viewer-independent. +// +// On error this returns true rather than false. A token that cannot read +// permissions would otherwise make the whole check silently pass everyone. +async function isTeamMember(octokit, owner, repo, username) { + try { + const { data } = await octokit.repos.getCollaboratorPermissionLevel({ + owner, + repo, + username, + }); + // Never log the level itself: job logs are public. + return TEAM_PERMISSIONS.includes(data.permission); + } catch (error) { + console.log( + `Cannot read repository permission for \`${username}\` (HTTP ${error.status}); applying the limit anyway.` + ); + return true; + } +} + function buildComment(author, total, limit, openPrs) { const listed = openPrs .slice(0, MAX_LISTED_PRS) @@ -69,7 +91,6 @@ async function upsertComment(octokit, params, body) { const [owner, repo] = process.env.GITHUB_REPOSITORY.split("/"); const prNumber = Number(process.env.PR_NUMBER); const author = process.env.PR_AUTHOR; - const association = process.env.PR_AUTHOR_ASSOCIATION; const rawLimit = Number(process.env.MAX_OPEN_PRS); const limit = Number.isInteger(rawLimit) && rawLimit >= 0 ? rawLimit : 5; @@ -78,13 +99,13 @@ async function upsertComment(octokit, params, body) { return; } - if (!MEMBER_ASSOCIATIONS.includes(association)) { - summary(`Skipping \`${author}\`: association is \`${association}\`, not an org member.`); + const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); + + if (!(await isTeamMember(octokit, owner, repo, author))) { + summary(`Skipping \`${author}\`: not a team member.`); return; } - const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); - const allOpen = await octokit.paginate(octokit.pulls.list, { owner, repo, diff --git a/.github/workflows/pr-open-limit.yml b/.github/workflows/pr-open-limit.yml index d22929911a..de5b723cb2 100644 --- a/.github/workflows/pr-open-limit.yml +++ b/.github/workflows/pr-open-limit.yml @@ -44,5 +44,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.pull_request.number }} PR_AUTHOR: ${{ github.event.pull_request.user.login }} - PR_AUTHOR_ASSOCIATION: ${{ github.event.pull_request.author_association }} MAX_OPEN_PRS: ${{ vars.MAX_OPEN_PRS_PER_AUTHOR || 5 }}