feat: request maintainer access for fork PRs without edit permissions (#26750)

- Update devin-conflict-resolver.yml to check maintainerCanModify for fork PRs
- Instead of skipping fork PRs entirely, now checks if maintainer access is enabled
- Posts friendly comment asking contributors to enable 'Allow edits from maintainers'
- Adds 'maintainer-access-requested' label to track which PRs have been notified
- Update stale-pr-devin-completion.yml to post similar comment when triggered
- Both workflows avoid duplicate comments by checking for existing requests

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Keith Williams
2026-01-12 13:39:15 +00:00
committed by GitHub
co-authored by Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent dd4e1ba11b
commit 023dfc2a5e
2 changed files with 135 additions and 7 deletions
+75 -2
View File
@@ -86,6 +86,8 @@ jobs:
const conflictingPRs = [];
const unknownPRs = [];
const forksNeedingMaintainerAccess = [];
function processPR(pr, mergeableStatus) {
const isTargetPR = manualPrNumber && pr.number === manualPrNumber;
const isFork = pr.headRepository?.owner?.login !== owner;
@@ -96,8 +98,22 @@ jobs:
}
if (!isTargetPR && isFork) {
console.log(`PR #${pr.number} is from a fork, skipping`);
return { skip: true };
if (!pr.maintainerCanModify) {
const hasRequestedLabel = pr.labels.nodes.some(label => label.name === 'maintainer-access-requested');
if (!hasRequestedLabel) {
console.log(`PR #${pr.number} is from a fork without maintainer access, will request access`);
forksNeedingMaintainerAccess.push({
number: pr.number,
title: pr.title,
author: pr.headRepository?.owner?.login,
html_url: pr.url
});
} else {
console.log(`PR #${pr.number} already has maintainer-access-requested label, skipping`);
}
return { skip: true };
}
console.log(`PR #${pr.number} is from a fork with maintainer access enabled`);
}
if (!isTargetPR) {
@@ -199,12 +215,15 @@ jobs:
}
console.log(`Found ${conflictingPRs.length} PRs with conflicts that need Devin sessions`);
console.log(`Found ${forksNeedingMaintainerAccess.length} fork PRs needing maintainer access`);
const fs = require('fs');
fs.writeFileSync('/tmp/conflicting-prs.json', JSON.stringify(conflictingPRs));
fs.writeFileSync('/tmp/forks-needing-access.json', JSON.stringify(forksNeedingMaintainerAccess));
core.setOutput('has-conflicts', conflictingPRs.length > 0 ? 'true' : 'false');
core.setOutput('conflict-count', conflictingPRs.length.toString());
core.setOutput('has-forks-needing-access', forksNeedingMaintainerAccess.length > 0 ? 'true' : 'false');
- name: Handle Devin sessions for conflicting PRs
if: steps.check-prs.outputs.has-conflicts == 'true'
@@ -444,3 +463,57 @@ jobs:
await new Promise(resolve => setTimeout(resolve, 1000));
}
- name: Request maintainer access for fork PRs
if: steps.check-prs.outputs.has-forks-needing-access == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const forksNeedingAccess = JSON.parse(fs.readFileSync('/tmp/forks-needing-access.json', 'utf8'));
const { owner, repo } = context.repo;
for (const pr of forksNeedingAccess) {
console.log(`Requesting maintainer access for PR #${pr.number}: ${pr.title}`);
try {
const commentBody = [
'### Maintainer Access Needed',
'',
`Hi @${pr.author}! Thanks for your contribution to Cal.com.`,
'',
'We noticed that this PR doesn\'t have "Allow edits from maintainers" enabled. We\'d love to help keep your PR up to date by resolving merge conflicts and making small fixes when needed.',
'',
'**Could you please enable this setting?** Here\'s how:',
'1. Scroll down to the bottom of this PR page',
'2. In the right sidebar, check the box that says **"Allow edits and access to secrets by maintainers"**',
'',
'This allows us to push commits directly to your PR branch, which helps us:',
'- Resolve merge conflicts automatically',
'- Make small adjustments to help get your PR merged faster',
'',
'If you have any concerns about enabling this setting, feel free to let us know!'
].join('\n');
await github.rest.issues.createComment({
owner,
repo,
issue_number: pr.number,
body: commentBody
});
await github.rest.issues.addLabels({
owner,
repo,
issue_number: pr.number,
labels: ['maintainer-access-requested']
});
console.log(`Posted comment and added label to PR #${pr.number}`);
} catch (error) {
console.error(`Error requesting maintainer access for PR #${pr.number}: ${error.message}`);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
@@ -61,11 +61,7 @@ jobs:
const isFork = pr.head.repo.fork || pr.head.repo.full_name !== pr.base.repo.full_name;
const maintainerCanModify = pr.maintainer_can_modify;
const canProceed = !isFork || maintainerCanModify;
if (!canProceed) {
core.setFailed(`Cannot complete this fork PR: the fork owner has not enabled "Allow edits from maintainers". PR author needs to enable this setting.`);
return;
}
const needsMaintainerAccess = isFork && !maintainerCanModify;
const headRepoFullName = pr.head.repo.full_name;
const headRepoCloneUrl = pr.head.repo.clone_url;
@@ -79,6 +75,7 @@ jobs:
core.setOutput('head_repo_clone_url', headRepoCloneUrl);
core.setOutput('maintainer_can_modify', maintainerCanModify);
core.setOutput('can_proceed', canProceed);
core.setOutput('needs_maintainer_access', needsMaintainerAccess);
console.log(`PR #${prNumber}: "${pr.title}"`);
console.log(`Author: ${pr.user.login}`);
@@ -86,6 +83,64 @@ jobs:
console.log(`Is fork: ${isFork}`);
console.log(`Head repo: ${headRepoFullName}`);
console.log(`Maintainer can modify: ${maintainerCanModify}`);
console.log(`Needs maintainer access: ${needsMaintainerAccess}`);
- name: Request maintainer access for fork PR
if: steps.pr.outputs.needs_maintainer_access == 'true'
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
PR_AUTHOR: ${{ steps.pr.outputs.pr_author }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = parseInt(process.env.PR_NUMBER);
const prAuthor = process.env.PR_AUTHOR;
const { owner, repo } = context.repo;
const comments = await github.rest.issues.listComments({
owner,
repo,
issue_number: prNumber
});
const alreadyRequested = comments.data.some(comment =>
comment.body?.includes('### Maintainer Access Needed')
);
if (!alreadyRequested) {
const commentBody = [
'### Maintainer Access Needed',
'',
`Hi @${prAuthor}! Thanks for your contribution to Cal.com.`,
'',
`We'd like to help complete this stale PR, but we noticed that "Allow edits from maintainers" isn't enabled. We need this setting to push updates to your PR branch.`,
'',
'**Could you please enable this setting?** Here\'s how:',
'1. Scroll down to the bottom of this PR page',
'2. In the right sidebar, check the box that says **"Allow edits and access to secrets by maintainers"**',
'',
'This allows us to push commits directly to your PR branch, which helps us:',
'- Complete any remaining work on your PR',
'- Fix merge conflicts and make small adjustments',
'- Get your contribution merged faster',
'',
'Once you\'ve enabled this setting, we\'ll be able to help finish up this PR. If you have any concerns about enabling this setting, feel free to let us know!'
].join('\n');
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: commentBody
});
console.log(`Posted maintainer access request comment on PR #${prNumber}`);
} else {
console.log(`Maintainer access already requested on PR #${prNumber}`);
}
core.setFailed(`Cannot complete this fork PR: the fork owner has not enabled "Allow edits from maintainers". A comment has been posted requesting access.`);
- name: Create Devin session
if: steps.pr.outputs.can_proceed == 'true'