[WIP] Add CI job priority system#2204
Conversation
…ties # Conflicts: # .github/workflows/collectivex-sweep.yml
There was a problem hiding this comment.
This PR is WIP and touches CI scheduling/authorization logic, so it warrants human review regardless of the inline nits already flagged. Beyond those, I also checked whether the free-text priority/runner workflow_dispatch inputs spliced into the runs-on JSON string (e.g. speedbench-al.yml) could be used to inject extra array elements and redirect a job onto an unintended runner — ruled out: any injected elements become additional required labels under GitHub's AND-matching for self-hosted runners, so they can only narrow matching, never bypass a label or redirect scheduling.
Extended reasoning...
Checked the label-injection candidate (unescaped format() splicing into fromJSON() for runs-on) independently: GitHub self-hosted runner matching requires a runner to possess ALL listed labels (AND semantics), so any extra elements an attacker splices into the JSON array via inputs.priority/inputs.runner can only add more required labels, narrowing which runners match rather than loosening or redirecting to a different/more-privileged runner. Combined with workflow_dispatch already requiring write access to trigger, this isn't an exploitable escalation path. This is in addition to, not a replacement for, the inline nit findings already posted (rounding-mode mismatch, skip-queue YAML config drift, tz-naive plan crash, unvalidated free-text priority input) — none of which are blocking, but the PR overall (skip_queue bypass authorization, priority scheduler affecting real GPU CI dispatch) is exactly the kind of security/infra-adjacent change that should get a human pass before merge.
Priority-aware CI scheduling
GitHub Actions does not natively prioritize self-hosted jobs. This PR adds an opt-in controller that decides which queued GPU job each compatible idle runner may accept.
How dispatch works
With priority scheduling enabled, a job requests labels similar to:
self-hostedb200ci-priority-6.000ci-queue-a81f23...Runners permanently carry only their normal hardware and identity labels. They do not permanently carry priority or queue labels.
The controller:
The priority label transports the score through GitHub's queued-job API. The queue token uniquely identifies one job. Because every label must match, the token prevents GitHub from selecting another job with the same hardware and priority.
Running jobs are never preempted or relabeled.
Single-node and multinode awareness
Existing runner labels remain compatibility constraints:
b200b200-multinodeA single-node runner cannot accept a multinode job unless it already satisfies the multinode label. The controller schedules the coordinating GitHub runner; existing Slurm launchers continue provisioning worker nodes.
Automatic scoring
All ordinary priorities are numeric, nonnegative, automatic, and uncapped. Higher scores run first.
For PR sweeps, Claude Fable inspects the diff and returns only verified priority facts, such as topology, workload type, precision, speculative decoding, framework, model family, checklist completion, and patchwork.
Fable never receives weights and never calculates a score.
ci_priority.py:configs/ci-priority.yaml.ci-priority-NUMBERlabel.This intersection keeps mixed matrices correct: a PR may contain both vLLM and SGLang jobs, but each job receives only its matching criteria. Main-branch pushes retain deterministic matrix-field scoring without a Fable call.
Malformed or unavailable Fable output conservatively classifies the PR as patchwork. Arbitrary
ci-priority:pNoverrides are not supported.Ties are resolved by queue timestamp and job ID. Runner selection preserves scarce exact-match runners when a less-constrained runner can accept the higher-priority job.
Queue tokens
Generated sweep tokens are deterministic hashes of:
Manual matrix workflows use the run ID and matrix-cell ID. Tokens are routing identifiers, not credentials or secrets.
Without a queue token, assigning
ci-priority-6.000could release any compatible priority-6.000job. The unique token makes the controller's assignment target exactly one queued job.Protected
skip_queueskip_queueis the only absolute bypass mechanism. The workflow keeps the job's ordinary numeric priority and adds a PR-scoped skip request label.The trusted controller then:
SemiAnalysisAI/Coremembership.Unauthorized or unverifiable requests retain their numeric priority.
REPO_PATremains inside the trusted controller and is never passed to PR-controlled workflow code.Required
REPO_PATpermissions:Safety and failure behavior
Activation requires deploying the controller and setting:
PRIORITY_SCHEDULER_ENABLED=trueVerification
actionlint.skip_queueoutrankedp100.000while retaining its numeric job label.