diff --git a/.github/workflows/publish-e2e-evidence.yml b/.github/workflows/publish-e2e-evidence.yml index 3d7fe32d78..c4f22c21d4 100644 --- a/.github/workflows/publish-e2e-evidence.yml +++ b/.github/workflows/publish-e2e-evidence.yml @@ -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 diff --git a/scripts/ci/publish_e2e_evidence.py b/scripts/ci/publish_e2e_evidence.py index 7d5de45627..8105259063 100644 --- a/scripts/ci/publish_e2e_evidence.py +++ b/scripts/ci/publish_e2e_evidence.py @@ -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 diff --git a/tests/ci/test_publish_e2e_evidence.py b/tests/ci/test_publish_e2e_evidence.py index e24a2d971e..365f344225 100644 --- a/tests/ci/test_publish_e2e_evidence.py +++ b/tests/ci/test_publish_e2e_evidence.py @@ -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 = "\n\npending\n"