Skip to content

Commit 5bcd08d

Browse files
authored
ci: request "Allow edits by maintainers" on fork PRs (#205)
Fork PRs often need small maintainer fixes — a rebase, a lint tweak, a follow-up commit — and each one stalls on a review round-trip when the author hasn't enabled "Allow edits by maintainers". This adds a workflow that checks the setting on fork PRs and, when it's off, leaves a comment asking the author to enable it, linking the GitHub docs page ([Allowing changes to a pull request branch created from a fork](https://docs.github.com/en/pull-requests/how-tos/work-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork)) that shows where the checkbox is. The check is non-blocking — the job never fails the PR. The main review point is the trigger: it runs on `pull_request_target` because on fork PRs the `pull_request` event's `GITHUB_TOKEN` is read-only and can't comment — exactly the PRs this targets. That's safe here since the workflow never checks out or executes PR code; it only reads PR metadata and writes an issue comment, with permissions scoped to `pull-requests: write`. A hidden HTML marker keeps it to a single comment per PR, updated in place when the state changes — including flipping to a short "enabled, thanks" note once the author turns it on (the checkbox emits no webhook event, so that refresh happens on the next push or a manual re-run). Same-repo branches are skipped because maintainers already have push access, and organization-owned forks are skipped because GitHub doesn't offer the setting there at all. Signed-off-by: Kevin Cui <bh@bugs.cc>
1 parent 0eddfbf commit 5bcd08d

1 file changed

Lines changed: 118 additions & 0 deletions

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
name: PR Maintainer Edits
2+
3+
# Asks fork-PR authors to enable "Allow edits by maintainers" so maintainers can
4+
# push small fixes (rebases, lint tweaks, follow-ups) directly to the PR branch
5+
# instead of a review round-trip. Non-blocking: it only leaves one comment and
6+
# keeps it in sync with the current setting; it never fails the PR.
7+
#
8+
# Why `pull_request_target` instead of `pull_request`: on fork PRs the
9+
# `pull_request` GITHUB_TOKEN is read-only, so the workflow could not comment on
10+
# exactly the PRs it targets. `pull_request_target` is safe here because this
11+
# workflow never checks out or executes PR code — it only reads PR metadata and
12+
# writes an issue comment.
13+
#
14+
# Toggling the checkbox emits no webhook event, so the comment is refreshed on
15+
# the next push (`synchronize`) or a manual re-run of this workflow.
16+
on:
17+
pull_request_target:
18+
types: [opened, reopened, synchronize]
19+
20+
# One run per PR; a newer push supersedes an in-flight check.
21+
concurrency:
22+
group: maintainer-edits-${{ github.event.pull_request.number }}
23+
cancel-in-progress: true
24+
25+
# Least privilege: read PR metadata + write one comment. No contents access.
26+
permissions:
27+
pull-requests: write
28+
29+
jobs:
30+
check:
31+
name: Check "Allow edits by maintainers"
32+
runs-on: ubuntu-latest
33+
# Only fork PRs — on same-repo branches maintainers already have push access.
34+
if: github.event.pull_request.head.repo.full_name != github.repository
35+
steps:
36+
- name: Comment when maintainer edits are disabled
37+
uses: actions/github-script@v7
38+
with:
39+
script: |
40+
const marker = '<!-- maintainer-edits-check -->';
41+
const docsUrl =
42+
'https://docs.github.com/en/pull-requests/how-tos/work-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork';
43+
44+
// Re-fetch instead of trusting the event payload so re-runs and
45+
// later pushes observe the author toggling the setting.
46+
const { data: pr } = await github.rest.pulls.get({
47+
owner: context.repo.owner,
48+
repo: context.repo.repo,
49+
pull_number: context.payload.pull_request.number,
50+
});
51+
52+
if (!pr.head.repo) {
53+
core.info('Head repository was deleted; nothing to request.');
54+
return;
55+
}
56+
// GitHub does not support maintainer edits on PRs from
57+
// organization-owned forks (the checkbox does not exist there),
58+
// so there is nothing to ask the author for.
59+
if (pr.head.repo.owner.type === 'Organization') {
60+
core.info(`Fork ${pr.head.repo.full_name} is organization-owned; maintainer edits cannot be enabled.`);
61+
return;
62+
}
63+
64+
const warningBody = [
65+
marker,
66+
`👋 @${pr.user.login} — thanks for the pull request!`,
67+
'',
68+
'It looks like **"Allow edits by maintainers"** is not enabled on this PR. Enabling it lets maintainers push small fixes (rebases, lint tweaks, follow-ups) directly to your branch, which usually gets your PR merged faster.',
69+
'',
70+
'Only the PR author can enable it:',
71+
'',
72+
'1. Open this PR page.',
73+
'2. In the right sidebar, check **"Allow edits by maintainers"** (at the bottom).',
74+
'',
75+
`See the GitHub docs: [Allowing changes to a pull request branch created from a fork](${docsUrl})`,
76+
'',
77+
'_This is appreciated but not required — it does not block review. This comment updates automatically on the next push once the setting is enabled._',
78+
].join('\n');
79+
80+
const resolvedBody = [
81+
marker,
82+
'✅ **"Allow edits by maintainers" is enabled — thank you!** Maintainers can now push small fixes directly to this branch when needed.',
83+
].join('\n');
84+
85+
const comments = await github.paginate(github.rest.issues.listComments, {
86+
owner: context.repo.owner,
87+
repo: context.repo.repo,
88+
issue_number: pr.number,
89+
per_page: 100,
90+
});
91+
const existing = comments.find((c) => c.body && c.body.includes(marker));
92+
93+
// Keep a single comment in sync with the current setting: create it
94+
// only when the setting is off, update it in place on state changes
95+
// (including the author later turning the setting back off).
96+
const desired = pr.maintainer_can_modify ? resolvedBody : warningBody;
97+
if (existing) {
98+
if (existing.body !== desired) {
99+
await github.rest.issues.updateComment({
100+
owner: context.repo.owner,
101+
repo: context.repo.repo,
102+
comment_id: existing.id,
103+
body: desired,
104+
});
105+
}
106+
} else if (!pr.maintainer_can_modify) {
107+
await github.rest.issues.createComment({
108+
owner: context.repo.owner,
109+
repo: context.repo.repo,
110+
issue_number: pr.number,
111+
body: warningBody,
112+
});
113+
}
114+
core.info(
115+
pr.maintainer_can_modify
116+
? 'Maintainer edits are enabled.'
117+
: 'Maintainer edits are disabled; requested via comment.'
118+
);

0 commit comments

Comments
 (0)