diff --git a/.github/workflows/auto-tune.yml b/.github/workflows/auto-tune.yml index 0afc79bb7c8c..16ad5d23b177 100644 --- a/.github/workflows/auto-tune.yml +++ b/.github/workflows/auto-tune.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: jobs: - lint: + auto-tune-lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/pr-test-npu.yml b/.github/workflows/pr-test-npu.yml index 237f18a27e49..c1e6ac5ab057 100644 --- a/.github/workflows/pr-test-npu.yml +++ b/.github/workflows/pr-test-npu.yml @@ -410,7 +410,7 @@ jobs: cd python python3 sglang/multimodal_gen/test/run_suite.py --suite 8-npu - pr-test-finish: + pr-test-npu-finish: needs: [ check-changes, diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a14997b9cffd..850ecd409b5c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -81,6 +81,12 @@ repos: language: system files: ^\.github/CI_PERMISSIONS\.json$ pass_filenames: false + - id: check-workflow-job-names + name: check for duplicate workflow job names + entry: python3 scripts/ci/check_workflow_job_names.py + language: system + files: ^\.github/workflows/.*\.yml$ + pass_filenames: false - repo: https://github.com/lycheeverse/lychee.git rev: lychee-v0.22.0 hooks: diff --git a/scripts/ci/check_workflow_job_names.py b/scripts/ci/check_workflow_job_names.py new file mode 100755 index 000000000000..75e2009ea1d5 --- /dev/null +++ b/scripts/ci/check_workflow_job_names.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"""Check that required status check job names are unique across workflows. + +Duplicate job names on the same commit allow a passing job in one workflow +to satisfy a required status check meant for a different workflow, bypassing +branch protection. + +See: https://github.com/sgl-project/sglang/pull/20208 for an example where +pr-test-npu.yml's "pr-test-finish" job (which passed) caused GitHub to treat +the required "pr-test-finish" check (from pr-test.yml, which failed) as met. +""" + +import glob +import sys +from collections import defaultdict + +import yaml + +# Job names used as required status checks in branch protection. +# These MUST be unique across all workflow files. +PROTECTED_JOB_NAMES = { + "pr-test-finish", + "lint", +} + + +def main() -> int: + workflows = sorted(glob.glob(".github/workflows/*.yml")) + job_to_files: dict[str, list[str]] = defaultdict(list) + + for wf in workflows: + with open(wf) as f: + data = yaml.safe_load(f) + if not data or "jobs" not in data: + continue + for job in data["jobs"]: + if job in PROTECTED_JOB_NAMES: + job_to_files[job].append(wf) + + duplicates = {job: files for job, files in job_to_files.items() if len(files) > 1} + + if not duplicates: + return 0 + + print("ERROR: Required status check job names must be unique across workflows.") + print("Duplicates allow branch protection bypass via auto-merge.\n") + for job, files in sorted(duplicates.items()): + print(f" Job '{job}' appears in:") + for f in files: + print(f" - {f}") + print() + + print("Fix: rename the job in non-primary workflows to avoid collision.") + return 1 + + +if __name__ == "__main__": + sys.exit(main())