From 783ac12071ec319d7d3e5af29d039034115d8934 Mon Sep 17 00:00:00 2001 From: "Kevin H. Luu" Date: Fri, 9 Oct 2026 15:43:37 -0700 Subject: [PATCH] [CI] Accept whitespace around /ci commands and reply instead of going silent (#60925) Signed-off-by: khluu Co-authored-by: Claude Opus 5.5 --- .github/workflows/run-ci-command.yml | 29 +++++++------- .github/workflows/scripts/run_ci_command.py | 34 ++++++++++++++++- .../workflows/scripts/test_run_ci_command.py | 38 ++++++++++++++++++- 3 files changed, 82 insertions(+), 19 deletions(-) diff --git a/.github/workflows/run-ci-command.yml b/.github/workflows/run-ci-command.yml index 3435148ea4..b0764bfa06 100644 --- a/.github/workflows/run-ci-command.yml +++ b/.github/workflows/run-ci-command.yml @@ -18,22 +18,8 @@ jobs: if: >- github.repository_owner == 'vllm-project' && github.event.issue.pull_request && - (github.event.comment.body == '/ci run' || - github.event.comment.body == '/ci run all' || - github.event.comment.body == '/ci run nightly' || - github.event.comment.body == '/ci run --allow-stale' || - github.event.comment.body == '/ci run all --allow-stale' || - github.event.comment.body == '/ci run nightly --allow-stale' || - github.event.comment.body == '/ci retry' || - github.event.comment.body == '/ci cancel' || - github.event.comment.body == '/amd-ci run' || - github.event.comment.body == '/amd-ci run all' || - github.event.comment.body == '/amd-ci run nightly' || - github.event.comment.body == '/amd-ci run --allow-stale' || - github.event.comment.body == '/amd-ci run all --allow-stale' || - github.event.comment.body == '/amd-ci run nightly --allow-stale' || - github.event.comment.body == '/amd-ci retry' || - github.event.comment.body == '/amd-ci cancel') + (startsWith(github.event.comment.body, '/ci') || + startsWith(github.event.comment.body, '/amd-ci')) runs-on: [self-hosted, linux, x64, vllm-runners] steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -54,3 +40,14 @@ jobs: BUILDKITE_PIPELINE: ci CI_TRUSTED_USERS: ${{ vars.CI_TRUSTED_USERS }} GH_TOKEN: ${{ github.token }} + - name: Report failure on the PR + if: failure() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + await github.rest.issues.createComment({ + ...context.repo, + issue_number: context.issue.number, + body: `❌ @${context.payload.comment.user.login}, your CI command failed with an unexpected error, so CI may not have started. See the [workflow run](${runUrl}) for details.`, + }); diff --git a/.github/workflows/scripts/run_ci_command.py b/.github/workflows/scripts/run_ci_command.py index 530bd697d0..537efa83c4 100644 --- a/.github/workflows/scripts/run_ci_command.py +++ b/.github/workflows/scripts/run_ci_command.py @@ -58,6 +58,7 @@ AMD_CI_COMMANDS = frozenset( RETRY_COMMANDS = frozenset({COMMAND_RETRY_FAILED, COMMAND_RETRY_AMD_FAILED}) CANCEL_COMMANDS = frozenset({COMMAND_CANCEL_CI, COMMAND_CANCEL_AMD_CI}) ALL_CI_COMMANDS = UPSTREAM_CI_COMMANDS | AMD_CI_COMMANDS +CI_COMMAND_PREFIXES = frozenset({"/ci", "/amd-ci"}) CI_AUTHORIZED_COMMENT_MARKER = "" READY_LABELS = {"ready", "ready-run-all-tests"} TRUSTED_PERMISSIONS = {"admin", "maintain", "write"} @@ -461,11 +462,17 @@ class BuildkiteClient: def parse_command(body: str) -> str | None: + body = body.strip() if body in ALL_CI_COMMANDS: return body return None +def is_ci_command_attempt(body: str) -> bool: + words = body.split(maxsplit=1) + return bool(words) and words[0].casefold() in CI_COMMAND_PREFIXES + + def pipeline_for_command( command: str, *, @@ -1086,6 +1093,28 @@ def handle_cancel_ci( ) +def reply_unrecognized_command( + event: Mapping[str, Any], + github: GitHubClient, +) -> None: + if "pull_request" not in event["issue"]: + return + issue_number = event["issue"]["number"] + comment_id = event["comment"]["id"] + actor = event["comment"]["user"]["login"] + if is_already_handled(github, issue_number, comment_id): + print(f"Comment {comment_id} was already handled.") + return + add_reaction_safely(github, comment_id, "-1") + commands = ", ".join(f"`{command}`" for command in sorted(ALL_CI_COMMANDS)) + github.add_comment( + issue_number, + f"❌ @{actor}, that is not a recognized CI command, so no CI was " + "started. Post a new comment containing only one of: " + f"{commands}.\n\n{command_comment_marker(comment_id)}", + ) + + def run( event: Mapping[str, Any], github: GitHubClient, @@ -1217,8 +1246,11 @@ def main() -> None: ) return - command = parse_command(event["comment"]["body"]) + body = event["comment"]["body"] + command = parse_command(body) if not command: + if is_ci_command_attempt(body): + reply_unrecognized_command(event, github) return pipeline = pipeline_for_command( diff --git a/.github/workflows/scripts/test_run_ci_command.py b/.github/workflows/scripts/test_run_ci_command.py index 9750cefaa3..623b412476 100644 --- a/.github/workflows/scripts/test_run_ci_command.py +++ b/.github/workflows/scripts/test_run_ci_command.py @@ -32,10 +32,12 @@ from run_ci_command import ( has_trusted_approval, is_active_build, is_build_for_pr, + is_ci_command_attempt, notify_authorized, parse_command, parse_trusted_users, pipeline_for_command, + reply_unrecognized_command, resolve_workflow_run_pr, run, select_latest_build, @@ -340,14 +342,46 @@ class RunCiCommandTest(unittest.TestCase): self.assertIsNone(parse_command("/ci run please")) self.assertIsNone(parse_command("/ci run all please")) self.assertIsNone(parse_command("/ci cancel please")) - self.assertIsNone(parse_command(" /ci run")) self.assertIsNone(parse_command("/amd-ci run please")) - self.assertIsNone(parse_command("/amd-ci retry ")) self.assertIsNone(parse_command("/AMD-CI run")) self.assertIsNone(parse_command("/amdci run")) self.assertIsNone(parse_command("/ci run --allow-stale all")) self.assertIsNone(parse_command("/amd-ci run --allow-stale please")) + def test_surrounding_whitespace_is_ignored(self) -> None: + self.assertEqual(parse_command("/ci run\r\n"), COMMAND_RUN_CI) + self.assertEqual(parse_command(" /ci run "), COMMAND_RUN_CI) + self.assertEqual(parse_command("/amd-ci retry\n"), COMMAND_RETRY_AMD_FAILED) + + def test_ci_command_attempts_are_detected(self) -> None: + for body in ("/ci", "/ci rn", "/CI run", "/amd-ci run please", " /ci\nrun"): + with self.subTest(body=body): + self.assertTrue(is_ci_command_attempt(body)) + for body in ("", "LGTM", "/cifoo", "/amdci run", "please /ci run"): + with self.subTest(body=body): + self.assertFalse(is_ci_command_attempt(body)) + + def test_unrecognized_command_gets_feedback(self) -> None: + github = FakeGitHub() + + reply_unrecognized_command(make_event("/ci rn", actor="author"), github) + + self.assertEqual(github.reactions, ["-1"]) + self.assertEqual(len(github.comments), 1) + self.assertIn( + "@author, that is not a recognized CI command", github.comments[0] + ) + self.assertIn(f"`{COMMAND_RUN_CI}`", github.comments[0]) + self.assertIn("", github.comments[0]) + + def test_unrecognized_command_feedback_is_not_repeated(self) -> None: + github = FakeGitHub(comments=["❌ earlier\n\n"]) + + reply_unrecognized_command(make_event("/ci rn"), github) + + self.assertEqual(len(github.comments), 1) + self.assertEqual(github.reactions, []) + def test_commands_select_only_their_configured_pipeline(self) -> None: cases = ( (COMMAND_RUN_CI, "ci"),