Use slash command to trigger CI (#13512)

Co-authored-by: sglang-bot <sglangbot@gmail.com>
This commit is contained in:
Lianmin Zheng
2025-11-18 04:39:58 -08:00
committed by GitHub
parent 7bc99d4164
commit f6cfe9f197
3 changed files with 178 additions and 1 deletions

View File

@@ -36,7 +36,9 @@ __Note__: Difference between Merge Oncall and Codeowner
If you meet any issues during the merge, you can discuss in [slack channels](https://slack.sglang.ai/): #dev, #pull-request, and #ci-cd-build-release.
## The List of Merge Oncalls and Reviewers
TODO
Now we have many Merge Oncalls mainly because the CI is flaky and the CODEOWNERS is too coarse-grained.
In the future, we hope the CI can be improved and we only need bypass rarely. After that, most Merge Oncalls can be converted back to Write and CODEOWNERS.
This list is based on the current situation. If you or someone you know would like to take on more responsibility and are qualified, please ping @Lianmin Zheng and @Ying Sheng in the Slack channel. They will start a nomination and internal review process.

View File

@@ -0,0 +1,46 @@
name: Slash Command Handler
on:
issue_comment:
types: [created]
permissions:
contents: read
pull-requests: write # Required to add labels and reactions
actions: write # Required to rerun workflows
issues: write # Required for comment reactions in some contexts
jobs:
slash_command:
# Only run if it is a PR and the comment starts with a recognized command
if: >
github.event.issue.pull_request &&
(startsWith(github.event.comment.body, '/tag-run-ci-label') ||
startsWith(github.event.comment.body, '/rerun-failed-ci'))
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
# We checkout the current context to get the script,
# but the script will fetch permissions from main as requested.
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install dependencies
run: |
pip install requests PyGithub
- name: Handle Slash Command
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO_FULL_NAME: ${{ github.repository }}
PR_NUMBER: ${{ github.event.issue.number }}
COMMENT_ID: ${{ github.event.comment.id }}
COMMENT_BODY: ${{ github.event.comment.body }}
USER_LOGIN: ${{ github.event.comment.user.login }}
run: |
python scripts/ci/slash_command_handler.py

View File

@@ -0,0 +1,129 @@
import json
import os
import sys
import requests
from github import Github
# Configuration
PERMISSIONS_FILE_URL = "https://raw.githubusercontent.com/sgl-project/sglang/main/.github/CI_PERMISSIONS.json"
def get_env_var(name):
val = os.getenv(name)
if not val:
print(f"Error: Environment variable {name} not set.")
sys.exit(1)
return val
def load_permissions(user_login):
"""
Downloads the permissions JSON from the main branch and returns
the permissions dict for the specific user.
"""
try:
print(f"Fetching permissions from {PERMISSIONS_FILE_URL}...")
response = requests.get(PERMISSIONS_FILE_URL)
response.raise_for_status()
data = response.json()
user_perms = data.get(user_login)
if not user_perms:
print(f"User '{user_login}' not found in permissions file.")
return None
return user_perms
except Exception as e:
print(f"Failed to load or parse permissions file: {e}")
sys.exit(1)
def handle_tag_run_ci(gh_repo, pr, comment, user_perms):
"""
Handles the /tag-run-ci-label command.
"""
if not user_perms.get("can_tag_run_ci_label", False):
print("Permission denied: can_tag_run_ci_label is false.")
return
print("Permission granted. Adding 'run-ci' label.")
pr.add_to_labels("run-ci")
# React to the comment with +1
comment.create_reaction("+1")
print("Label added and comment reacted.")
def handle_rerun_failed_ci(gh_repo, pr, comment, user_perms):
"""
Handles the /rerun-failed-ci command.
"""
if not user_perms.get("can_rerun_failed_ci", False):
print("Permission denied: can_rerun_failed_ci is false.")
return
print("Permission granted. Triggering rerun of failed workflows.")
# Get the SHA of the latest commit in the PR
head_sha = pr.head.sha
print(f"Checking workflows for commit: {head_sha}")
# List all workflow runs for this commit
runs = gh_repo.get_workflow_runs(head_sha=head_sha)
rerun_count = 0
for run in runs:
# We only care about completed runs that failed
if run.status == "completed" and run.conclusion == "failure":
print(f"Rerunning workflow: {run.name} (ID: {run.id})")
run.rerun_failed()
rerun_count += 1
if rerun_count > 0:
comment.create_reaction("+1")
print(f"Triggered rerun for {rerun_count} failed workflows.")
else:
print("No failed workflows found to rerun.")
def main():
# 1. Load Environment Variables
token = get_env_var("GITHUB_TOKEN")
repo_name = get_env_var("REPO_FULL_NAME")
pr_number = int(get_env_var("PR_NUMBER"))
comment_id = int(get_env_var("COMMENT_ID"))
comment_body = get_env_var("COMMENT_BODY").strip()
user_login = get_env_var("USER_LOGIN")
# 2. Initialize GitHub API
g = Github(token)
repo = g.get_repo(repo_name)
pr = repo.get_pull(pr_number)
comment = repo.get_issue(pr_number).get_comment(comment_id)
# 3. Load Permissions (Remote Check)
user_perms = load_permissions(user_login)
if not user_perms:
print(f"User {user_login} does not have any configured permissions. Exiting.")
return
# 4. Parse Command and Execute
# split lines to handle cases where there might be text after the command
first_line = comment_body.split("\n")[0].strip()
if first_line.startswith("/tag-run-ci-label"):
handle_tag_run_ci(repo, pr, comment, user_perms)
elif first_line.startswith("/rerun-failed-ci"):
handle_rerun_failed_ci(repo, pr, comment, user_perms)
else:
print(f"Unknown or ignored command: {first_line}")
if __name__ == "__main__":
main()