Skip to content

Commit e0ce310

Browse files
authored
fix: improve handling of pull requests from forked repositories to prevent errors (#182)
1 parent 443fb6a commit e0ce310

File tree

2 files changed

+56
-26
lines changed

2 files changed

+56
-26
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ jobs:
123123
> [!IMPORTANT]
124124
> `pr-comments` is an experimental feature. By default, it's disabled.
125125
>
126-
> This feature currently doesn’t work with forked repositories. For more details, refer to issue [#77](https://github.com/commit-check/commit-check-action/issues/77).
126+
> PR comments are skipped for pull requests from forked repositories. For more details, refer to issue [`#143`](https://github.com/commit-check/commit-check-action/issues/143).
127127

128128
Note: the default rule of above inputs is following [this configuration](https://github.com/commit-check/commit-check-action/blob/main/commit-check.toml). If you want to customize, just add your [`commit-check.toml`](https://commit-check.github.io/commit-check/configuration.html) config file under your repository root directory.
129129

main.py

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#!/usr/bin/env python3
2+
import json
23
import os
34
import sys
45
import subprocess
56
import re
6-
from github import Github, Auth # type: ignore
7+
from github import Github, Auth, GithubException # type: ignore
78

89

910
# Constants for message titles
@@ -96,73 +97,102 @@ def add_job_summary() -> int:
9697
return 0 if result_text is None else 1
9798

9899

100+
def is_fork_pr() -> bool:
101+
"""Returns True when the triggering PR originates from a forked repository."""
102+
event_path = os.getenv("GITHUB_EVENT_PATH")
103+
if not event_path:
104+
return False
105+
try:
106+
with open(event_path, "r") as f:
107+
event = json.load(f)
108+
pr = event.get("pull_request", {})
109+
head_full_name = pr.get("head", {}).get("repo", {}).get("full_name", "")
110+
base_full_name = pr.get("base", {}).get("repo", {}).get("full_name", "")
111+
return bool(
112+
head_full_name and base_full_name and head_full_name != base_full_name
113+
)
114+
except Exception:
115+
return False
116+
117+
99118
def add_pr_comments() -> int:
100119
"""Posts the commit check result as a comment on the pull request."""
101120
if PR_COMMENTS == "false":
102121
return 0
103122

123+
# Fork PRs triggered by the pull_request event receive a read-only token;
124+
# the GitHub API will always reject comment writes with 403.
125+
if is_fork_pr():
126+
print(
127+
"::warning::Skipping PR comment: pull requests from forked repositories "
128+
"cannot write comments via the pull_request event (GITHUB_TOKEN is "
129+
"read-only for forks). Use the pull_request_target event or the "
130+
"two-workflow artifact pattern instead. "
131+
"See https://github.com/commit-check/commit-check-action/issues/77"
132+
)
133+
return 0
134+
104135
try:
105136
token = os.getenv("GITHUB_TOKEN")
106137
repo_name = os.getenv("GITHUB_REPOSITORY")
107138
pr_number = os.getenv("GITHUB_REF")
108139
if pr_number is not None:
109140
pr_number = pr_number.split("/")[-2]
110141
else:
111-
# Handle the case where GITHUB_REF is not set
112142
raise ValueError("GITHUB_REF environment variable is not set")
113143

114-
# Initialize GitHub client
115-
# Use new Auth API to avoid deprecation warning
116144
if not token:
117145
raise ValueError("GITHUB_TOKEN is not set")
146+
118147
g = Github(auth=Auth.Token(token))
119148
repo = g.get_repo(repo_name)
120149
pull_request = repo.get_issue(int(pr_number))
121150

122151
# Prepare comment content
123152
result_text = read_result_file()
124-
pr_comments = (
153+
pr_comment_body = (
125154
SUCCESS_TITLE
126155
if result_text is None
127156
else f"{FAILURE_TITLE}\n```\n{result_text}\n```"
128157
)
129158

130159
# Fetch all existing comments on the PR
131160
comments = pull_request.get_comments()
161+
matching_comments = [
162+
c
163+
for c in comments
164+
if c.body.startswith(SUCCESS_TITLE) or c.body.startswith(FAILURE_TITLE)
165+
]
132166

133-
# Track if we found a matching comment
134-
matching_comments = []
135-
last_comment = None
136-
137-
for comment in comments:
138-
if comment.body.startswith(SUCCESS_TITLE) or comment.body.startswith(
139-
FAILURE_TITLE
140-
):
141-
matching_comments.append(comment)
142167
if matching_comments:
143168
last_comment = matching_comments[-1]
144-
145-
if last_comment.body == pr_comments:
169+
if last_comment.body == pr_comment_body:
146170
print(f"PR comment already up-to-date for PR #{pr_number}.")
147171
return 0
148-
else:
149-
# If the last comment doesn't match, update it
150-
print(f"Updating the last comment on PR #{pr_number}.")
151-
last_comment.edit(pr_comments)
152-
153-
# Delete all older matching comments
172+
print(f"Updating the last comment on PR #{pr_number}.")
173+
last_comment.edit(pr_comment_body)
154174
for comment in matching_comments[:-1]:
155175
print(f"Deleting an old comment on PR #{pr_number}.")
156176
comment.delete()
157177
else:
158-
# No matching comments, create a new one
159178
print(f"Creating a new comment on PR #{pr_number}.")
160-
pull_request.create_comment(body=pr_comments)
179+
pull_request.create_comment(body=pr_comment_body)
161180

162181
return 0 if result_text is None else 1
182+
except GithubException as e:
183+
if e.status == 403:
184+
print(
185+
"::warning::Unable to post PR comment (403 Forbidden). "
186+
"Ensure your workflow grants 'issues: write' permission. "
187+
f"Error: {e.data.get('message', str(e))}",
188+
file=sys.stderr,
189+
)
190+
return 0
191+
print(f"Error posting PR comment: {e}", file=sys.stderr)
192+
return 0
163193
except Exception as e:
164194
print(f"Error posting PR comment: {e}", file=sys.stderr)
165-
return 1
195+
return 0
166196

167197

168198
def log_error_and_exit(

0 commit comments

Comments
 (0)