Repository navigation
tech-debt(ci): GitHubApi is shared by three modules but lives inside a size-frozen one, so the shared client can no longer gain a method #15411
Description
What
GitHubApi — the REST client for every CI pipeline script — is defined at
pipeline-scripts/ci_dispatch_watchdog.py:693, inside a module that is grandfathered at its
#14236 size ceiling with zero headroom:
pipeline-scripts/ci_dispatch_watchdog.py: 1487 lines, ceiling 1487
Three modules already depend on it:
pipeline-scripts/ci_dispatch_watchdog.py(owner)pipeline-scripts/ci_red_cause.pypipeline-scripts/ci_dispatch_retry.py
A shared client that cannot grow is not shared — it is frozen. Any new call any consumer needs
must be implemented outside the client, in the consumer, which is the fork this repository's
canonical rules exist to prevent.
This already happened, today
PR #15396 added two calls the retry sweep needs — rerun_run (POST .../rerun-failed-jobs) and
rate_limit_remaining (GET /rate_limit). Put on GitHubApi where they belong, they took the
file to 1520 lines against the 1487 ceiling and python_file_size_ratchet_test failed, with
the correct message:
a grandfathered file may not grow (#14236) — the exemption freezes the size it was granted for,
it does not license more
The ratchet is right. So both functions now live in pipeline-scripts/ci_dispatch_retry.py as
module-level helpers over api.request(...), and GitHubApi — the thing that should own an
actions/runs/{id}/rerun-failed-jobs call — does not have one. The next consumer that needs a
re-run will either import from ci_dispatch_retry (a script, not a client) or write its own.
Nothing is wrong with either change in isolation. The structure guarantees the fork.
Fix
Extract GitHubApi into its own module — e.g. pipeline-scripts/github_api.py — and have
ci_dispatch_watchdog, ci_red_cause and ci_dispatch_retry import it. Two effects:
- The shared client can gain methods again, so
rerun_runandrate_limit_remainingmove back
where they belong. ci_dispatch_watchdog.pydrops well below its ceiling, and the shrink gets locked in per
tech-debt(ci): the file-size ratchet says 'raising a ceiling must fail' but only blocks raises above the frozen baseline, so a shrink can be spent back #14498 — the ratchet only ever tightens.
This is a move, not a rewrite: no behaviour changes and the existing tests cover it.
Second, smaller consolidation in the same family
pipeline-scripts/ci_red_cause.py:113-126 models a closed vocabulary as bare strings plus
frozensets:
CAUSE_STARVATION = "runner-starvation"
CAUSE_PROVISIONING = "provisioning-failure"
CAUSE_TEST = "test-failure"
CAUSE_SUPERSEDED = "superseded"
CAUSE_UNDETERMINED = "undetermined"
INFRASTRUCTURE_CAUSES = frozenset({CAUSE_STARVATION, CAUSE_PROVISIONING})
REAL_FAILURE_CAUSES = frozenset({CAUSE_TEST, CAUSE_UNDETERMINED})That is an enum with its membership axes expressed as side tables — the exact shape #14263
catalogues across 62 status enums. It works today and is well documented, so this is an
opportunity rather than a defect: a StrEnum with the two groupings as classmethods would make an
unhandled cause a type error rather than a silent CAUSE_UNDETERMINED.
Worth doing with the extraction above, since both touch the same three files, and worth
not doing alone.
Tasks
- Extract
GitHubApitopipeline-scripts/github_api.py; update the three importers - Move
rerun_runandrate_limit_remainingfromci_dispatch_retry.pyonto the client - Lock in the resulting
ci_dispatch_watchdog.pyshrink in the ratchet baseline (tech-debt(ci): the file-size ratchet says 'raising a ceiling must fail' but only blocks raises above the frozen baseline, so a shrink can be spent back #14498) - Consider
CAUSE_*→StrEnumwithis_infrastructure/is_real_failureas members
Acceptance criteria
-
GitHubApiis defined once, outside any file at its size ceiling - No consumer defines its own REST call against the Actions API
-
ci_dispatch_watchdog.py's recorded ceiling is reduced to its new size, not left at 1487 -
repo_tests/ci_dispatch_watchdog_test.pyandci_dispatch_retry_test.pypass unchanged
Related
- tech-debt(lint): the file-size hook's grandfather list has no ceiling — the three exempted files grew to 1.4x-6.8x the limit #14236 / tech-debt(ci): the file-size ratchet says 'raising a ceiling must fail' but only blocks raises above the frozen baseline, so a shrink can be spent back #14498 — the size ratchet that surfaced this, working exactly as intended
- Umbrella: 62 status enums with no way to tell an axis from a fork — AgentStatus is defined 3 times, and the consolidation module was itself duplicated #14263 — the enum-vs-fork catalogue the
CAUSE_*set belongs to - ci: a run that never gets a runner is reported as
failure, indistinguishable from a real test failure #15139 / PR fix(ci): re-dispatch infrastructure-caused reds, and close the #15394 merge_group gaps (#15139) #15396 — where the fork was forced
Activity
fix(docker): stop shipping the backlog CLI that imports an excluded t…
mrveiss commentedon Oct 9, 2026
Sighting from widening the duplication-guard scan to
pipeline-scripts/(#17377), jscpd 5.0.6: a 9-linebuild_api()" clone betweenpipeline-scripts/ci_red_cause.pyandpipeline-scripts/release_sync_main.py`. This is the same shared-client class as this issue. The duplication guard ratchets it once #17377 lands.