Skip to content

Commit e42cc5c

Browse files
MishaKavclaude
andauthored
Fix fork PR permission error with specific guidance (#243) (#245)
Detect fork PRs and show targeted error message suggesting pull_request_target event instead of generic permissions advice. Add Fork PRs troubleshooting section to README. Closes #243 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7939938 commit e42cc5c

3 files changed

Lines changed: 95 additions & 18 deletions

File tree

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ A GitHub Action that adds pytest coverage reports as comments to your pull reque
4545
- [Multiple Files (Monorepo)](#multiple-files-monorepo)
4646
- [🔧 Troubleshooting](#-troubleshooting)
4747
- [Comment Not Appearing](#comment-not-appearing)
48+
- [Fork PRs](#fork-prs)
4849
- [Unrecognized Arguments Error](#unrecognized-arguments-error)
4950
- [Coverage Report Too Large](#coverage-report-too-large)
5051
- [GitHub Step Summary Too Large](#github-step-summary-too-large)
@@ -603,6 +604,26 @@ Instead of a badge image:
603604

604605
**Why it works on forks but not main repos**: Forks often have different default permission settings than the main repository. Organizations frequently set restrictive defaults for security.
605606

607+
### Fork PRs
608+
609+
**Issue**: Permission denied when a pull request is opened from a fork.
610+
611+
**Root Cause**: GitHub restricts the `GITHUB_TOKEN` to **read-only** for `pull_request` events triggered from forks. This is a security measure — even if your workflow has `pull-requests: write`, the token is downgraded for fork PRs.
612+
613+
**Solution**: Use the `pull_request_target` event, which runs in the context of the base branch and gets a token with write permissions:
614+
615+
```yaml
616+
on:
617+
pull_request_target:
618+
types: [opened, synchronize, reopened]
619+
620+
permissions:
621+
contents: read
622+
pull-requests: write
623+
```
624+
625+
> **Security Warning**: `pull_request_target` runs with access to the base repo's secrets and a write token. Never checkout and run untrusted code from the fork with these elevated permissions. Only check out the base branch code, or carefully limit what fork code is executed.
626+
606627
### Unrecognized Arguments Error
607628

608629
**Issue**: `pytest: error: unrecognized arguments: --cov --cov-report`

dist/index.js

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39398,11 +39398,41 @@ const truncateSummary = (content, maxLength) => {
3939839398
};
3939939399

3940039400
const handlePermissionError = (error, context) => {
39401-
const eventName = context?.eventName || 'this event';
39402-
if (error?.status === 403) {
39403-
const helpfulMessage = [
39404-
'Permission denied when trying to create/update comment.',
39401+
if (error?.status !== 403) {
39402+
core.setFailed(`Failed to create/update comment: ${error.message}`);
39403+
throw error;
39404+
}
39405+
39406+
const isForkPR = context?.payload?.pull_request?.head?.repo?.fork === true;
39407+
const lines = ['Permission denied when trying to create/update comment.', ''];
39408+
39409+
if (isForkPR) {
39410+
lines.push(
39411+
'This PR is from a fork. GitHub restricts the GITHUB_TOKEN to read-only',
39412+
'for fork PRs triggered by the `pull_request` event.',
39413+
'',
39414+
'To fix this, use the `pull_request_target` event instead:',
39415+
'',
39416+
'```yaml',
39417+
'on:',
39418+
' pull_request_target:',
39419+
' types: [opened, synchronize, reopened]',
39420+
'',
39421+
'permissions:',
39422+
' contents: read',
39423+
' pull-requests: write',
39424+
'```',
39425+
'',
39426+
'Note: `pull_request_target` runs in the context of the base branch.',
39427+
'Be cautious when checking out fork code — never run untrusted code',
39428+
'from the fork with elevated permissions.',
3940539429
'',
39430+
'For more information, see:',
39431+
'https://github.com/MishaKav/pytest-coverage-comment#fork-prs',
39432+
);
39433+
} else {
39434+
const eventName = context?.eventName || 'this event';
39435+
lines.push(
3940639436
'This error usually occurs because the GITHUB_TOKEN lacks necessary permissions.',
3940739437
'',
3940839438
'To fix this, add a permissions block to your workflow:',
@@ -39415,12 +39445,10 @@ const handlePermissionError = (error, context) => {
3941539445
'',
3941639446
`For ${eventName === 'push' ? 'push events creating commit comments' : 'pull request events and more information'}, see:`,
3941739447
'https://github.com/MishaKav/pytest-coverage-comment#comment-not-appearing',
39418-
].join('\n');
39419-
39420-
core.setFailed(helpfulMessage);
39421-
} else {
39422-
core.setFailed(`Failed to create/update comment: ${error.message}`);
39448+
);
3942339449
}
39450+
39451+
core.setFailed(lines.join('\n'));
3942439452
throw error;
3942539453
};
3942639454

src/index.js

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,41 @@ const truncateSummary = (content, maxLength) => {
8787
};
8888

8989
const handlePermissionError = (error, context) => {
90-
const eventName = context?.eventName || 'this event';
91-
if (error?.status === 403) {
92-
const helpfulMessage = [
93-
'Permission denied when trying to create/update comment.',
90+
if (error?.status !== 403) {
91+
core.setFailed(`Failed to create/update comment: ${error.message}`);
92+
throw error;
93+
}
94+
95+
const isForkPR = context?.payload?.pull_request?.head?.repo?.fork === true;
96+
const lines = ['Permission denied when trying to create/update comment.', ''];
97+
98+
if (isForkPR) {
99+
lines.push(
100+
'This PR is from a fork. GitHub restricts the GITHUB_TOKEN to read-only',
101+
'for fork PRs triggered by the `pull_request` event.',
102+
'',
103+
'To fix this, use the `pull_request_target` event instead:',
104+
'',
105+
'```yaml',
106+
'on:',
107+
' pull_request_target:',
108+
' types: [opened, synchronize, reopened]',
109+
'',
110+
'permissions:',
111+
' contents: read',
112+
' pull-requests: write',
113+
'```',
114+
'',
115+
'Note: `pull_request_target` runs in the context of the base branch.',
116+
'Be cautious when checking out fork code — never run untrusted code',
117+
'from the fork with elevated permissions.',
94118
'',
119+
'For more information, see:',
120+
'https://github.com/MishaKav/pytest-coverage-comment#fork-prs',
121+
);
122+
} else {
123+
const eventName = context?.eventName || 'this event';
124+
lines.push(
95125
'This error usually occurs because the GITHUB_TOKEN lacks necessary permissions.',
96126
'',
97127
'To fix this, add a permissions block to your workflow:',
@@ -104,12 +134,10 @@ const handlePermissionError = (error, context) => {
104134
'',
105135
`For ${eventName === 'push' ? 'push events creating commit comments' : 'pull request events and more information'}, see:`,
106136
'https://github.com/MishaKav/pytest-coverage-comment#comment-not-appearing',
107-
].join('\n');
108-
109-
core.setFailed(helpfulMessage);
110-
} else {
111-
core.setFailed(`Failed to create/update comment: ${error.message}`);
137+
);
112138
}
139+
140+
core.setFailed(lines.join('\n'));
113141
throw error;
114142
};
115143

0 commit comments

Comments
 (0)