From 666044ddf9ed4990cbaf9acadb73dbd6330ae47c Mon Sep 17 00:00:00 2001 From: Stephanie Anderson Date: Wed, 1 Apr 2026 13:47:33 +0200 Subject: [PATCH 1/2] feat(validate-pr): Skip checks for users with write access Users with write repository access (admin, maintain, or write role) now bypass PR validation. Maintainer-only checks (reopening closed PRs, counting as maintainer in issue discussions) remain restricted to admin/maintain roles. Co-Authored-By: Claude Opus 4.6 (1M context) --- validate-pr/scripts/validate-pr.js | 40 ++++++++++++++++++------------ 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/validate-pr/scripts/validate-pr.js b/validate-pr/scripts/validate-pr.js index 0aba25f..6412e86 100644 --- a/validate-pr/scripts/validate-pr.js +++ b/validate-pr/scripts/validate-pr.js @@ -34,26 +34,34 @@ module.exports = async ({ github, context, core }) => { return; } - // --- Helper: check if a user has admin or maintain permission on a repo (cached) --- - const maintainerCache = new Map(); - async function isMaintainer(owner, repoName, username) { + // --- Helpers: check user permission on a repo (cached) --- + const roleCache = new Map(); + async function getRole(owner, repoName, username) { const key = `${owner}/${repoName}:${username}`; - if (maintainerCache.has(key)) return maintainerCache.get(key); - let result = false; + if (roleCache.has(key)) return roleCache.get(key); + let roleName = null; try { const { data } = await github.rest.repos.getCollaboratorPermissionLevel({ owner, repo: repoName, username, }); - // permission field uses legacy values (admin/write/read/none) where - // maintain maps to write. Use role_name for the actual role. - result = ['admin', 'maintain'].includes(data.role_name); + roleName = data.role_name; } catch { - // noop — result stays false + // noop — roleName stays null } - maintainerCache.set(key, result); - return result; + roleCache.set(key, roleName); + return roleName; + } + + async function hasWriteAccess(owner, repoName, username) { + const role = await getRole(owner, repoName, username); + return ['admin', 'maintain', 'write'].includes(role); + } + + async function isMaintainer(owner, repoName, username) { + const role = await getRole(owner, repoName, username); + return ['admin', 'maintain'].includes(role); } // --- Step 1: Skip if a maintainer reopened the PR --- @@ -67,14 +75,14 @@ module.exports = async ({ github, context, core }) => { } } - // --- Step 2: Check if PR author is a maintainer (admin or maintain role) --- - const authorIsMaintainer = await isMaintainer(repo.owner, repo.repo, prAuthor); - if (authorIsMaintainer) { - core.info(`PR author ${prAuthor} has admin/maintain access. Skipping.`); + // --- Step 2: Check if PR author has write access (admin, maintain, or write role) --- + const authorHasWriteAccess = await hasWriteAccess(repo.owner, repo.repo, prAuthor); + if (authorHasWriteAccess) { + core.info(`PR author ${prAuthor} has write+ access. Skipping.`); core.setOutput('skipped', 'true'); return; } - core.info(`PR author ${prAuthor} is not a maintainer.`); + core.info(`PR author ${prAuthor} does not have write access.`); // --- Step 3: Parse issue references from PR body --- const body = pullRequest.body || ''; From 3ea332a1037da31f0d286af29c9cfe2eae64e821 Mon Sep 17 00:00:00 2001 From: Stephanie Anderson Date: Wed, 1 Apr 2026 14:10:34 +0200 Subject: [PATCH 2/2] fix(validate-pr): Use correct role_name value for write access The GitHub API returns "push" (not "write") for the role_name field. Keep "write" in the list for forward compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) --- validate-pr/scripts/validate-pr.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/validate-pr/scripts/validate-pr.js b/validate-pr/scripts/validate-pr.js index 6412e86..113e55b 100644 --- a/validate-pr/scripts/validate-pr.js +++ b/validate-pr/scripts/validate-pr.js @@ -56,7 +56,8 @@ module.exports = async ({ github, context, core }) => { async function hasWriteAccess(owner, repoName, username) { const role = await getRole(owner, repoName, username); - return ['admin', 'maintain', 'write'].includes(role); + // role_name values: admin, maintain, push, triage, pull (+ custom roles) + return ['admin', 'maintain', 'push', 'write'].includes(role); } async function isMaintainer(owner, repoName, username) {