Extracts CI's priority-based runner orchestration ("traffic-control") out of the large inline-bash step in ci.yml into a checked-in, unit-tested Python module: .github/scripts/traffic_control.py (+ test_traffic_control.py).
Design: pure decision CORE + thin gh-I/O SHELL
The testability win is a network-free decision core:
runs_to_cancel(this_pr, all_prs, self_run_id=…) — PASS 1: run ids to cancel. A strictly-lower OTHER PR's active/queued run is cancelled iff (a) THIS PR is P0 (an emergency reclaims all strictly-lower runners), or (b) the target is broken/draft (effective priority 10) and THIS PR is strictly-higher (a wasted run any ready PR may reclaim). P1–P9 never bump a normal lower run; a broken/draft PR (P10) preempts nothing. Never self (by number or run-id), never equal-or-higher.
wait_blockers(this_pr, all_prs) — PASS 2: yield to any strictly-higher-priority PR with an active/queued run, and to same-level peers ordered ahead (running-first, then oldest createdAt). Empty list ⇒ proceed.
The shell (run_live) gathers the snapshot via gh, applies the cancellations, and runs the bounded hold-back poll loop; it always exits 0. --dry-run feeds the core a snapshot JSON and prints the decisions with zero network. No jq/bash dependency (cross-platform, per the repo's Python-stdlib convention).
ci.yml wiring
The traffic-control job now checks out the repo and runs python3 .github/scripts/traffic_control.py. Permissions gain contents: read (for the checkout) alongside the existing actions: write / pull-requests: read; env and the downstream needs: traffic-control wiring are unchanged; the step stays continue-on-error.
Safety invariants (preserved)
Never cancel a run on main/push (the gh run list query filters --event pull_request and drops headBranch == main), never cancel THIS PR's own run, never cancel an equal-or-higher-priority PR (so a P10 never preempts another P10).
Behaviour vs the old bash
Behaviour-preserving, plus the ticket's two refinements: (1) drafts now count as P10 (bottom); (2) an explicit same-level running-first-then-oldest tiebreaker. The old bash's broken-reclaim — any strictly-higher PR (not just P0) may cancel a broken target's run to reclaim its runner — is preserved and extended to drafts: any strictly-higher PR may reclaim a brokenordraft run, while P1–P9 still never bump a normal lower run (only P0 does that).
Tests
37 stdlib unittest cases: priority resolution (P-label / broken / draft / default P5); PASS 1 (P0 reclaims all strictly-lower incl. P10; a P3 reclaims a broken P10 and a draft P10; a P3 does not bump a normal P5; a P10 self preempts nothing; self / equal-or-higher / own-run-id never cancelled); PASS 2 same-level running-first/oldest ordering; end-to-end scenarios incl. a P5 that reclaims a draft and waits behind a higher PR.
Closes #342
## What
Extracts CI's priority-based runner orchestration ("traffic-control") out of the large inline-bash step in `ci.yml` into a checked-in, unit-tested Python module: **`.github/scripts/traffic_control.py`** (+ `test_traffic_control.py`).
## Design: pure decision CORE + thin gh-I/O SHELL
The testability win is a network-free decision core:
- `effective_priority(pr)` — lowest-numbered `P0`–`P9` label; default `P5`; **`broken` OR `draft` ⇒ 10** (bottom, below P9).
- `runs_to_cancel(this_pr, all_prs, self_run_id=…)` — **PASS 1**: run ids to cancel. A strictly-lower OTHER PR's active/queued run is cancelled iff **(a)** THIS PR is P0 (an emergency reclaims *all* strictly-lower runners), **or (b)** the target is broken/draft (effective priority 10) and THIS PR is strictly-higher (a wasted run any ready PR may reclaim). P1–P9 never bump a *normal* lower run; a broken/draft PR (P10) preempts nothing. Never self (by number or run-id), never equal-or-higher.
- `wait_blockers(this_pr, all_prs)` — **PASS 2**: yield to any strictly-higher-priority PR with an active/queued run, and to same-level peers ordered ahead (**running-first, then oldest `createdAt`**). Empty list ⇒ proceed.
The **shell** (`run_live`) gathers the snapshot via `gh`, applies the cancellations, and runs the bounded hold-back poll loop; it always exits 0. `--dry-run` feeds the core a snapshot JSON and prints the decisions with zero network. No `jq`/bash dependency (cross-platform, per the repo's Python-stdlib convention).
## ci.yml wiring
The `traffic-control` job now checks out the repo and runs `python3 .github/scripts/traffic_control.py`. Permissions gain `contents: read` (for the checkout) alongside the existing `actions: write` / `pull-requests: read`; `env` and the downstream `needs: traffic-control` wiring are unchanged; the step stays `continue-on-error`.
## Safety invariants (preserved)
Never cancel a run on `main`/push (the `gh run list` query filters `--event pull_request` and drops `headBranch == main`), never cancel THIS PR's own run, never cancel an equal-or-higher-priority PR (so a P10 never preempts another P10).
## Behaviour vs the old bash
Behaviour-preserving, plus the ticket's two refinements: **(1)** drafts now count as **P10** (bottom); **(2)** an explicit same-level **running-first-then-oldest** tiebreaker. The old bash's broken-reclaim — any strictly-higher PR (not just P0) may cancel a `broken` target's run to reclaim its runner — is **preserved and extended to drafts**: any strictly-higher PR may reclaim a `broken` **or** `draft` run, while P1–P9 still never bump a *normal* lower run (only P0 does that).
## Tests
37 stdlib `unittest` cases: priority resolution (P-label / broken / draft / default P5); PASS 1 (P0 reclaims all strictly-lower incl. P10; a P3 reclaims a broken P10 *and* a draft P10; a P3 does **not** bump a normal P5; a P10 self preempts nothing; self / equal-or-higher / own-run-id never cancelled); PASS 2 same-level running-first/oldest ordering; end-to-end scenarios incl. a P5 that reclaims a draft *and* waits behind a higher PR.
## Validation (no emulator/Gradle needed)
- `python -m unittest discover -s .github/scripts` → **37 pass**.
- `python -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))"` → **parses clean**.
- `python -m py_compile .github/scripts/traffic_control.py` → OK.
- `--dry-run` on sample PR-sets, incl. a **P3 self** that cancels a draft (`4401`) + broken (`4402`) run, spares a normal P5, and yields to a higher P1.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #342
What
Extracts CI's priority-based runner orchestration ("traffic-control") out of the large inline-bash step in
ci.ymlinto a checked-in, unit-tested Python module:.github/scripts/traffic_control.py(+test_traffic_control.py).Design: pure decision CORE + thin gh-I/O SHELL
The testability win is a network-free decision core:
effective_priority(pr)— lowest-numberedP0–P9label; defaultP5;brokenORdraft⇒ 10 (bottom, below P9).runs_to_cancel(this_pr, all_prs, self_run_id=…)— PASS 1: run ids to cancel. A strictly-lower OTHER PR's active/queued run is cancelled iff (a) THIS PR is P0 (an emergency reclaims all strictly-lower runners), or (b) the target is broken/draft (effective priority 10) and THIS PR is strictly-higher (a wasted run any ready PR may reclaim). P1–P9 never bump a normal lower run; a broken/draft PR (P10) preempts nothing. Never self (by number or run-id), never equal-or-higher.wait_blockers(this_pr, all_prs)— PASS 2: yield to any strictly-higher-priority PR with an active/queued run, and to same-level peers ordered ahead (running-first, then oldestcreatedAt). Empty list ⇒ proceed.The shell (
run_live) gathers the snapshot viagh, applies the cancellations, and runs the bounded hold-back poll loop; it always exits 0.--dry-runfeeds the core a snapshot JSON and prints the decisions with zero network. Nojq/bash dependency (cross-platform, per the repo's Python-stdlib convention).ci.yml wiring
The
traffic-controljob now checks out the repo and runspython3 .github/scripts/traffic_control.py. Permissions gaincontents: read(for the checkout) alongside the existingactions: write/pull-requests: read;envand the downstreamneeds: traffic-controlwiring are unchanged; the step stayscontinue-on-error.Safety invariants (preserved)
Never cancel a run on
main/push (thegh run listquery filters--event pull_requestand dropsheadBranch == main), never cancel THIS PR's own run, never cancel an equal-or-higher-priority PR (so a P10 never preempts another P10).Behaviour vs the old bash
Behaviour-preserving, plus the ticket's two refinements: (1) drafts now count as P10 (bottom); (2) an explicit same-level running-first-then-oldest tiebreaker. The old bash's broken-reclaim — any strictly-higher PR (not just P0) may cancel a
brokentarget's run to reclaim its runner — is preserved and extended to drafts: any strictly-higher PR may reclaim abrokenordraftrun, while P1–P9 still never bump a normal lower run (only P0 does that).Tests
37 stdlib
unittestcases: priority resolution (P-label / broken / draft / default P5); PASS 1 (P0 reclaims all strictly-lower incl. P10; a P3 reclaims a broken P10 and a draft P10; a P3 does not bump a normal P5; a P10 self preempts nothing; self / equal-or-higher / own-run-id never cancelled); PASS 2 same-level running-first/oldest ordering; end-to-end scenarios incl. a P5 that reclaims a draft and waits behind a higher PR.Validation (no emulator/Gradle needed)
python -m unittest discover -s .github/scripts→ 37 pass.python -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))"→ parses clean.python -m py_compile .github/scripts/traffic_control.py→ OK.--dry-runon sample PR-sets, incl. a P3 self that cancels a draft (4401) + broken (4402) run, spares a normal P5, and yields to a higher P1.🤖 Generated with Claude Code