Skip to content

Commit 71588dd

Browse files
feat(validate-pr): Skip checks for users with write access (#162)
* 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) <noreply@anthropic.com>
1 parent 02fd7a2 commit 71588dd

1 file changed

Lines changed: 25 additions & 16 deletions

File tree

‎validate-pr/scripts/validate-pr.js‎

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,26 +34,35 @@ module.exports = async ({ github, context, core }) => {
3434
return;
3535
}
3636

37-
// --- Helper: check if a user has admin or maintain permission on a repo (cached) ---
38-
constmaintainerCache=newMap();
39-
asyncfunctionisMaintainer(owner,repoName,username){
37+
// --- Helpers: check user permission on a repo (cached) ---
38+
constroleCache=newMap();
39+
asyncfunctiongetRole(owner,repoName,username){
4040
constkey=`${owner}/${repoName}:${username}`;
41-
if(maintainerCache.has(key))returnmaintainerCache.get(key);
42-
letresult=false;
41+
if(roleCache.has(key))returnroleCache.get(key);
42+
letroleName=null;
4343
try{
4444
const{ data }=awaitgithub.rest.repos.getCollaboratorPermissionLevel({
4545
owner,
4646
repo: repoName,
4747
username,
4848
});
49-
// permission field uses legacy values (admin/write/read/none) where
50-
// maintain maps to write. Use role_name for the actual role.
51-
result=['admin','maintain'].includes(data.role_name);
49+
roleName=data.role_name;
5250
}catch{
53-
// noop — result stays false
51+
// noop — roleName stays null
5452
}
55-
maintainerCache.set(key,result);
56-
returnresult;
53+
roleCache.set(key,roleName);
54+
returnroleName;
55+
}
56+
57+
asyncfunctionhasWriteAccess(owner,repoName,username){
58+
constrole=awaitgetRole(owner,repoName,username);
59+
// role_name values: admin, maintain, push, triage, pull (+ custom roles)
60+
return['admin','maintain','push','write'].includes(role);
61+
}
62+
63+
asyncfunctionisMaintainer(owner,repoName,username){
64+
constrole=awaitgetRole(owner,repoName,username);
65+
return['admin','maintain'].includes(role);
5766
}
5867

5968
// --- Step 1: Skip if a maintainer reopened the PR ---
@@ -67,14 +76,14 @@ module.exports = async ({ github, context, core }) => {
6776
}
6877
}
6978

70-
// --- Step 2: Check if PR author is a maintainer (adminor maintain role) ---
71-
constauthorIsMaintainer=awaitisMaintainer(repo.owner,repo.repo,prAuthor);
72-
if(authorIsMaintainer){
73-
core.info(`PR author ${prAuthor} has admin/maintain access. Skipping.`);
79+
// --- Step 2: Check if PR author has write access (admin, maintain, or write role) ---
80+
constauthorHasWriteAccess=awaithasWriteAccess(repo.owner,repo.repo,prAuthor);
81+
if(authorHasWriteAccess){
82+
core.info(`PR author ${prAuthor} has write+ access. Skipping.`);
7483
core.setOutput('skipped','true');
7584
return;
7685
}
77-
core.info(`PR author ${prAuthor}is not a maintainer.`);
86+
core.info(`PR author ${prAuthor}does not have write access.`);
7887

7988
// --- Step 3: Parse issue references from PR body ---
8089
constbody=pullRequest.body||'';

0 commit comments

Comments
 (0)