[CI] Fix CI authorization notification fallback (#51095)

Signed-off-by: khluu <khluu000@gmail.com> Co-authored-by: OpenAI Codex <noreply@openai.com>

Kevin H. Luu committed Aug 5, 2026 at 01:34 UTC a3b86752fbcd2fb1a9adfe44634ac0d955228e2a
5 files changed +97 -13
.github/workflows/add_label_automerge.yml
+1 -1
@@ -7,7 +7,7 @@ on:
7 - auto_merge_enabled
8 jobs:
9 add-label-on-auto-merge:
10 - runs-on: ubuntu-latest
10 + runs-on: [self-hosted, linux, x64, vllm-runners]
11 steps:
12 - name: Add label
13 uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
.github/workflows/notify-ci-authorized.yml
+4 -4
@@ -11,7 +11,8 @@ concurrency:
11 group: >-
12 notify-ci-authorized-${{
13 github.event.pull_request.number ||
14 - github.event.workflow_run.pull_requests[0].number
14 + github.event.workflow_run.pull_requests[0].number ||
15 + github.event.workflow_run.id
16 }}
17 cancel-in-progress: false
18
@@ -29,9 +30,8 @@ jobs:
30 github.event.label.name == 'ready-run-all-tests')) ||
31 (github.event_name == 'workflow_run' &&
32 github.event.workflow_run.event == 'pull_request_review' &&
32 - github.event.workflow_run.conclusion == 'success' &&
33 - github.event.workflow_run.pull_requests[0])
34 - runs-on: ubuntu-latest
33 + github.event.workflow_run.conclusion == 'success')
34 + runs-on: [self-hosted, linux, x64, vllm-runners]
35 steps:
36 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
37 with:
.github/workflows/record-ci-approval.yml
+1 -1
@@ -9,6 +9,6 @@ permissions: {}
9 jobs:
10 approved:
11 if: github.event.review.state == 'approved'
12 - runs-on: ubuntu-latest
12 + runs-on: [self-hosted, linux, x64, vllm-runners]
13 steps:
14 - run: echo "Approval recorded"
.github/workflows/scripts/run_ci_command.py
+44 -4
@@ -203,6 +203,13 @@ class GitHubClient:
203 def get_pr(self, number: int) -> dict[str, Any]:
204 return self._request(self._repo_path(f"/pulls/{number}"))
205
206 + def list_pulls_for_commit(self, commit: str) -> list[dict[str, Any]]:
207 + commit = urllib.parse.quote(commit, safe="")
208 + response = self._request(self._repo_path(f"/commits/{commit}/pulls"))
209 + if not isinstance(response, list):
210 + raise ApiError(None, "GitHub API returned an invalid pull request list.")
211 + return response
212 +
213 def get_permission(self, actor: str) -> str:
214 username = urllib.parse.quote(actor, safe="")
215 try:
@@ -622,7 +629,6 @@ def notify_authorized(
629 if (
630 event.get("action") != "labeled"
631 or event["label"]["name"] not in READY_LABELS
625 - or has_trusted_approval(github, pr["number"], trusted_users)
632 ):
633 return
634 elif "review" in event:
@@ -650,6 +656,40 @@ def notify_authorized(
656 )
657
658
659 +def resolve_workflow_run_pr(
660 + workflow_run: Mapping[str, Any],
661 + github: GitHubClient,
662 +) -> dict[str, Any] | None:
663 + head_sha = str(workflow_run.get("head_sha", ""))
664 + if not head_sha:
665 + return None
666 +
667 + seen: set[int] = set()
668 +
669 + def find_matching_pr(
670 + candidates: Sequence[Mapping[str, Any]],
671 + ) -> dict[str, Any] | None:
672 + for candidate in candidates:
673 + candidate_number = candidate.get("number")
674 + if not isinstance(candidate_number, int) or candidate_number in seen:
675 + continue
676 + seen.add(candidate_number)
677 + try:
678 + pr = github.get_pr(candidate_number)
679 + except ApiError as error:
680 + if error.status == 404:
681 + continue
682 + raise
683 + if pr["state"] == "open" and pr["head"]["sha"] == head_sha:
684 + return pr
685 + return None
686 +
687 + associated_pr = find_matching_pr(workflow_run.get("pull_requests") or [])
688 + if associated_pr is not None:
689 + return associated_pr
690 + return find_matching_pr(github.list_pulls_for_commit(head_sha))
691 +
692 +
693 def handle_run_ci(
694 *,
695 actor: str,
@@ -897,10 +937,10 @@ def main() -> None:
937 )
938 return
939 if event_name == "workflow_run":
900 - pull_requests = event["workflow_run"].get("pull_requests") or []
901 - if not pull_requests:
940 + pr = resolve_workflow_run_pr(event["workflow_run"], github)
941 + if pr is None:
942 + print("Could not resolve an open PR for the approval workflow run.")
943 return
903 - pr = github.get_pr(int(pull_requests[0]["number"]))
944 notify_authorized(
945 {
946 "action": "submitted",
.github/workflows/scripts/test_run_ci_command.py
+47 -3
@@ -15,6 +15,7 @@ from run_ci_command import (
15 COMMAND_RUN_CI_ALL,
16 COMMAND_RUN_CI_NIGHTLY,
17 RETRY_STATES,
18 + ApiError,
19 BuildkiteClient,
20 HttpTransport,
21 authorize,
@@ -25,6 +26,7 @@ from run_ci_command import (
26 notify_authorized,
27 parse_command,
28 parse_trusted_users,
29 + resolve_workflow_run_pr,
30 run,
31 select_latest_build,
32 )
@@ -72,6 +74,8 @@ class FakeGitHub:
74 review_decision: str = "REVIEW_REQUIRED",
75 reviews: list[dict[str, Any]] | None = None,
76 comments: list[str] | None = None,
77 + pulls_for_commit: list[dict[str, Any]] | None = None,
78 + prs: dict[int, dict[str, Any]] | None = None,
79 ) -> None:
80 self.comments = comments or []
81 self.permission = permission
@@ -80,13 +84,21 @@ class FakeGitHub:
84 self.reactions: list[str] = []
85 self.review_decision = review_decision
86 self.reviews = reviews or []
87 + self.pulls_for_commit = pulls_for_commit or []
88 + self.prs = prs or {self.pr["number"]: self.pr}
89
90 def get_pr(self, number: int) -> dict[str, Any]:
85 - return self.pr
91 + try:
92 + return self.prs[number]
93 + except KeyError as error:
94 + raise ApiError(404, "Not found") from error
95
96 def get_permission(self, actor: str) -> str:
97 return self.permissions.get(actor, self.permission)
98
99 + def list_pulls_for_commit(self, commit: str) -> list[dict[str, Any]]:
100 + return self.pulls_for_commit
101 +
102 def get_review_decision(self, number: int) -> str:
103 return self.review_decision
104
@@ -525,7 +537,7 @@ class RunCiCommandTest(unittest.TestCase):
537 self.assertNotIn(COMMAND_RUN_CI_NIGHTLY, comment)
538 self.assertIn(CI_AUTHORIZED_COMMENT_MARKER, comment)
539
528 - def test_ready_label_does_not_notify_after_trusted_approval(self) -> None:
540 + def test_ready_label_notifies_after_missed_approval_notification(self) -> None:
541 pr = make_pr(labels=[{"name": "ready"}])
542 event = {
543 "action": "labeled",
@@ -547,7 +559,8 @@ class RunCiCommandTest(unittest.TestCase):
559
560 notify_authorized(event, github)
561
550 - self.assertEqual(github.comments, [])
562 + self.assertEqual(len(github.comments), 1)
563 + self.assertIn("@author", github.comments[0])
564
565 def test_trusted_approval_notifies_author_once(self) -> None:
566 event = {
@@ -573,6 +586,37 @@ class RunCiCommandTest(unittest.TestCase):
586 self.assertEqual(len(github.comments), 1)
587 self.assertIn("@author", github.comments[0])
588
589 + def test_workflow_run_resolves_pr_from_head_commit(self) -> None:
590 + pr = make_pr()
591 + github = FakeGitHub(
592 + pr=pr,
593 + pulls_for_commit=[{"number": pr["number"]}],
594 + )
595 +
596 + resolved = resolve_workflow_run_pr(
597 + {"head_sha": pr["head"]["sha"], "pull_requests": []},
598 + github,
599 + )
600 +
601 + self.assertEqual(resolved, pr)
602 +
603 + def test_workflow_run_ignores_unrelated_pr_association(self) -> None:
604 + pr = make_pr()
605 + github = FakeGitHub(
606 + pr=pr,
607 + pulls_for_commit=[{"number": pr["number"]}],
608 + )
609 +
610 + resolved = resolve_workflow_run_pr(
611 + {
612 + "head_sha": pr["head"]["sha"],
613 + "pull_requests": [{"number": 1}],
614 + },
615 + github,
616 + )
617 +
618 + self.assertEqual(resolved, pr)
619 +
620 def test_approval_does_not_notify_when_ready_label_exists(self) -> None:
621 pr = make_pr(labels=[{"name": "ready"}])
622 event = {