From a7e00b7576151d6f653bc53eeb51ccb7a99467dc Mon Sep 17 00:00:00 2001 From: Alison Shao <54658187+alisonshao@users.noreply.github.com> Date: Wed, 9 Sep 2026 18:01:18 -0700 Subject: [PATCH] [CI] Answer unrecognized slash commands instead of skipping silently (#38736) --- .github/workflows/slash-command-handler.yml | 9 ++- .../developer_guide/contribution_guide.mdx | 2 + scripts/ci/utils/slash_command_handler.py | 69 ++++++++++++++++++- 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/.github/workflows/slash-command-handler.yml b/.github/workflows/slash-command-handler.yml index d8239f8fb..99884117d 100644 --- a/.github/workflows/slash-command-handler.yml +++ b/.github/workflows/slash-command-handler.yml @@ -14,6 +14,10 @@ jobs: slash_command: # Only run if it is a PR and the comment contains a recognized command # Use contains() since startsWith() can't handle leading whitespace/newlines + # + # The trailing startsWith() namespace clauses admit *unrecognized* commands + # so the script can answer them; they are startsWith() because + # contains('/run-') would fire on any comment quoting `scripts/run-tests.sh`. if: > github.event.issue.pull_request && (contains(github.event.comment.body, '/tag-run-ci-label') || @@ -24,7 +28,10 @@ jobs: contains(github.event.comment.body, '/rerun-full-ci') || contains(github.event.comment.body, '/rerun-extra-ci') || contains(github.event.comment.body, '/rerun-group') || - contains(github.event.comment.body, '/rerun-test')) + contains(github.event.comment.body, '/rerun-test') || + startsWith(github.event.comment.body, '/tag-') || + startsWith(github.event.comment.body, '/rerun-') || + startsWith(github.event.comment.body, '/run-')) runs-on: ubuntu-latest steps: diff --git a/docs/docs/developer_guide/contribution_guide.mdx b/docs/docs/developer_guide/contribution_guide.mdx index 527a10945..09005fa17 100644 --- a/docs/docs/developer_guide/contribution_guide.mdx +++ b/docs/docs/developer_guide/contribution_guide.mdx @@ -141,6 +141,8 @@ Selective reruns do not build or install a PR-local `sglang-kernel` wheel. If a If you have permission, the [Slash Command Handler](https://github.com/sgl-project/sglang/actions/workflows/slash-command-handler.yml) will run your command and react with a 👍 to your comment. It may take up to a few minutes for the reaction to appear. Here’s a usage [example](https://github.com/sgl-project/sglang/pull/14253#issuecomment-3599509302). +If a command isn't recognized, the handler reacts 😕 instead, and replies with the closest matching command when there is one. A comment with no reaction at all means the handler never saw it — check the command is spelled as listed above. + To avoid spamming a PR with too many `/rerun-failed-ci` comments, you can also trigger the command by editing an existing comment and adding any suffix (e.g., `/rerun-failed-ci try again`). If you don’t have permission and you’re not the PR author, please ask maintainers to trigger CI for you. diff --git a/scripts/ci/utils/slash_command_handler.py b/scripts/ci/utils/slash_command_handler.py index dad457678..c12e2aac5 100644 --- a/scripts/ci/utils/slash_command_handler.py +++ b/scripts/ci/utils/slash_command_handler.py @@ -1,3 +1,4 @@ +import difflib import glob import json import os @@ -1615,6 +1616,72 @@ def handle_rerun_group( ) +# Namespaces this handler owns. Anything else reaching the script is a comment +# that merely quoted a path, and is left alone. +OWNED_COMMAND_PREFIXES = ("/tag-", "/rerun-", "/run-") + +# Suggestion targets. Documented spellings only, so a typo is pointed at the +# canonical name rather than at an undocumented alias. +KNOWN_COMMANDS = ( + "/tag-run-ci-label", + "/tag-and-rerun-ci", + "/rerun-failed-ci", + "/rerun-group", + "/rerun-test", + "/run-full-ci", + "/run-extra-ci", +) + +# Removed commands, kept because they were documented long enough that muscle +# memory still sends them and difflib would suggest something unrelated. +RETIRED_COMMANDS = { + "/rerun-stage": ( + "`/rerun-stage` was removed. Use `/rerun-test ` for specific " + "tests, or `/rerun-failed-ci` for everything that didn't pass." + ), +} + + +def handle_unknown_command(pr, comment, first_line): + """ + Answer a comment addressed to this handler that matched no command. + + A silent skip is indistinguishable from the bot being down, so always react; + comment only when there is something concrete to say, to avoid turning every + stray `/run-...` into PR noise. + """ + tokens = first_line.split() + command = tokens[0] if tokens else first_line + if not command.startswith(OWNED_COMMAND_PREFIXES): + print(f"Not addressed to this handler: {first_line[:60]!r}") + return + + print(f"Unrecognized command: {command}") + try: + comment.create_reaction("confused") + except Exception as e: + print(f"Failed to add reaction: {e}") + + hint = RETIRED_COMMANDS.get(command) + if hint is None: + close = difflib.get_close_matches(command, KNOWN_COMMANDS, n=1, cutoff=0.6) + hint = f"Did you mean `{close[0]}`?" if close else None + + if hint is None: + print("No close match; reaction only.") + return + + try: + pr.create_issue_comment( + f"⛔ `{command}` isn't a recognized CI command. {hint}\n\n" + "See the [command reference]" + "(https://docs.sglang.io/docs/developer_guide/contribution_guide" + "#how-to-trigger-ci-tests)." + ) + except Exception as e: + print(f"Failed to post hint comment: {e}") + + def main(): # 1. Load Environment Variables token = get_env_var("GITHUB_TOKEN") @@ -1740,7 +1807,7 @@ def main(): ) else: - print(f"Unknown or ignored command: {first_line}") + handle_unknown_command(pr, comment, first_line) if __name__ == "__main__":