fix(ci): resolve fork PRs in the E2E evidence publisher

The publisher read the PR number from the CI run's pull_requests
payload. GitHub keeps that payload empty for fork runs, so the job
printed 'No pull request is associated' and stopped on every fork PR.

Resolve the PR from the run's head owner, branch, and SHA instead.
The SHA match skips runs that a newer push superseded.

A fork PR also has no CI review comment, because the live poller
skips forks. The publisher now logs this and exits clean instead of
raising; the evidence stays in the workflow artifact.
This commit is contained in:
ethernet
2026-08-10 17:33:09 -04:00
parent 9829746dfe
commit 3ee1bb323e
3 changed files with 48 additions and 4 deletions
+11 -2
View File
@@ -44,12 +44,21 @@ jobs:
GH_SESSION_TOKEN: ${{ secrets.GH_IMAGE_SESSION_TOKEN }}
SOURCE_REPO: ${{ github.repository }}
SOURCE_RUN_ID: ${{ github.event.workflow_run.id }}
HEAD_OWNER: ${{ github.event.workflow_run.head_repository.owner.login }}
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
set -euo pipefail
PR_NUMBER=$(gh api "repos/$SOURCE_REPO/actions/runs/$SOURCE_RUN_ID" --jq '.pull_requests[0].number // empty')
# The run's own ``pull_requests`` payload is always empty for a
# fork PR, so resolve the PR from its head reference instead.
# The head-SHA match skips runs that a newer push superseded.
PR_NUMBER=$(gh api -X GET "repos/$SOURCE_REPO/pulls" \
-f head="$HEAD_OWNER:$HEAD_BRANCH" -f state=open \
--jq '.[] | select(.head.sha == $ENV.HEAD_SHA) | .number' \
| head -n1)
if [ -z "$PR_NUMBER" ]; then
echo "No pull request is associated with CI run $SOURCE_RUN_ID."
echo "No open pull request has head $HEAD_OWNER:$HEAD_BRANCH at $HEAD_SHA (CI run $SOURCE_RUN_ID)."
exit 0
fi
+11 -2
View File
@@ -203,7 +203,7 @@ def _find_review_comment(comments: object) -> dict[str, Any] | None:
return None
def _wait_for_review_comment(token: str, source_repo: str, pr_number: str) -> dict[str, Any]:
def _wait_for_review_comment(token: str, source_repo: str, pr_number: str) -> dict[str, Any] | None:
"""Wait briefly for GitHub's comment API to expose the completed marker."""
request = urllib.request.Request(
f"{API_BASE}/repos/{source_repo}/issues/{pr_number}/comments?per_page=100",
@@ -221,7 +221,7 @@ def _wait_for_review_comment(token: str, source_repo: str, pr_number: str) -> di
return comment
if attempt + 1 < COMMENT_LOOKUP_ATTEMPTS:
time.sleep(COMMENT_LOOKUP_DELAY_SECONDS)
raise ValueError("CI review comment with E2E evidence marker is missing")
return None
def upload_evidence(
@@ -280,6 +280,15 @@ def publish(
print("No inline E2E evidence to publish.")
return False
comment = _wait_for_review_comment(token, source_repo, pr_number)
if comment is None:
# A fork PR gets no CI review comment (the live poller needs a
# write token there), so there is no marker to patch. The
# evidence stays available in the workflow artifact.
print(
f"PR #{pr_number} has no CI review comment with an E2E evidence "
"marker; the evidence stays in the workflow artifact."
)
return False
try:
attachment_urls = upload_evidence(
files, evidence_dir, source_repo, session_token
+26
View File
@@ -179,6 +179,32 @@ def test_publish_marks_evidence_upload_failure_in_pr_comment(tmp_path, monkeypat
]
def test_publish_skips_when_no_review_comment_exists(tmp_path, monkeypatch, capsys):
monkeypatch.setattr(
_mod,
"load_evidence",
lambda evidence_dir: (
[_mod.EvidenceFile("shot.png", "new screenshot: shot.png")],
{},
),
)
monkeypatch.setattr(_mod, "_wait_for_review_comment", lambda *args: None)
monkeypatch.setattr(
_mod,
"upload_evidence",
lambda *args: (_ for _ in ()).throw(AssertionError("must not upload")),
)
assert _mod.publish(
"github-token",
"NousResearch/hermes-agent",
tmp_path,
"83202",
"image-token",
) is False
assert "no CI review comment" in capsys.readouterr().out
def test_find_review_comment_requires_the_evidence_marker():
pending = "<!-- hermes-ci-review-bot -->\n<!-- hermes-e2e-evidence:start -->\npending\n<!-- hermes-e2e-evidence:end -->"