Skip to content

Commit 512f5cf

Browse files
committed
feat: add support for posting comments on pull requests from forked repositories
1 parent 443fb6a commit 512f5cf

File tree

1 file changed

+53
-25
lines changed

1 file changed

+53
-25
lines changed

main.py

Lines changed: 53 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,100 @@ 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(head_full_name and base_full_name and head_full_name != base_full_name)
112+
except Exception:
113+
return False
114+
115+
99116
def add_pr_comments() -> int:
100117
"""Posts the commit check result as a comment on the pull request."""
101118
if PR_COMMENTS == "false":
102119
return 0
103120

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

114-
# Initialize GitHub client
115-
# Use new Auth API to avoid deprecation warning
116142
if not token:
117143
raise ValueError("GITHUB_TOKEN is not set")
144+
118145
g = Github(auth=Auth.Token(token))
119146
repo = g.get_repo(repo_name)
120147
pull_request = repo.get_issue(int(pr_number))
121148

122149
# Prepare comment content
123150
result_text = read_result_file()
124-
pr_comments = (
151+
pr_comment_body = (
125152
SUCCESS_TITLE
126153
if result_text is None
127154
else f"{FAILURE_TITLE}\n```\n{result_text}\n```"
128155
)
129156

130157
# Fetch all existing comments on the PR
131158
comments = pull_request.get_comments()
159+
matching_comments = [
160+
c
161+
for c in comments
162+
if c.body.startswith(SUCCESS_TITLE) or c.body.startswith(FAILURE_TITLE)
163+
]
132164

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)
142165
if matching_comments:
143166
last_comment = matching_comments[-1]
144-
145-
if last_comment.body == pr_comments:
167+
if last_comment.body == pr_comment_body:
146168
print(f"PR comment already up-to-date for PR #{pr_number}.")
147169
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
170+
print(f"Updating the last comment on PR #{pr_number}.")
171+
last_comment.edit(pr_comment_body)
154172
for comment in matching_comments[:-1]:
155173
print(f"Deleting an old comment on PR #{pr_number}.")
156174
comment.delete()
157175
else:
158-
# No matching comments, create a new one
159176
print(f"Creating a new comment on PR #{pr_number}.")
160-
pull_request.create_comment(body=pr_comments)
177+
pull_request.create_comment(body=pr_comment_body)
161178

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

167195

168196
def log_error_and_exit(

0 commit comments

Comments
 (0)