ci: bound the required aggregate check and group the ungrouped PR workflows
Two invariants nothing in CI can complain about, because neither one makes anything fail. Both are measured, and the first commit shows the lint failing on main with exactly six findings.
ci-status had no timeout
A job without timeout-minutes inherits GitHub's six-hour default. ci-status is the required aggregate — the check every pull request in this repository is blocked on — and it runs one step that reads needs and decides. A hang there blocks the repository for six hours and reads as "still running" the whole time.
Every one of its ten siblings in ci.yml is bounded between 5 and 20 minutes, and across all five workflows that own a required context it was the only job without a bound. That makes it drift rather than intent, so it now matches web-status, the equivalent aggregate in web-validation.yml, at 5.
Five pull_request workflows had no concurrency group
ci-cache-receipts, cmux-cloud-cli, indexnow-tests, localization-catalog, r2-upload-tests. Without a group, each push starts a fresh run and lets the superseded ones finish. They are all path-filtered so they fire rarely — but when they fire it is on a branch someone is actively pushing to, which is exactly when superseded runs pile up.
They get a ref-keyed group that cancels only on pull_request, so a push to main is never interrupted. That is the idiom cloud-machine-tests.yml and cloud-task-local-tests.yml already use.
The lint
Both rules stay derived rather than listed. Required contexts are matched to the jobs that produce them, and a context that stops matching any job fails the test rather than quietly guarding nothing.
Having a group is the requirement, not what it does with a superseded run — repair-nightly-appcast-content-types.yml sets cancel-in-progress: false deliberately because it writes to R2, and serializing answers the same problem. My first draft flagged it; that was the lint being wrong, not the workflow.
The shape is borrowed from quarry's tests/test_ci_policy.py, which keeps this class of invariant in one readable file instead of hoping nobody regresses it.
Also measured, deliberately not changed
47 of 233 jobs repo-wide declare no timeout-minutes. None of them run on macOS — the expensive class is already fully bounded, which is why this PR does not touch them. The remaining ones are Linux, where a six-hour hang costs less, and picking 47 timeout values I cannot validate would trade a small bill for new flakiness. Worth a follow-up by someone who knows the expected runtimes.
ci-web.yml has three untimed jobs and zero runs in recent history despite being edited on 2026-09-21. I did not touch it; someone should confirm whether it is still meant to fire.
🤖 Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.
Summary by cubic
Bound the required ci-status aggregate job and give five path-filtered pull_request workflows a concurrency group, so a hung check can't block the repo for six hours and superseded runs don't pile up on active branches.
ci-statusgets a 5-minute timeout, matchingweb-status; every other job inci.ymlwas already bounded.ci-cache-receipts,cmux-cloud-cli,indexnow-tests,localization-catalog, andr2-upload-testsget a ref-keyed group that cancels only onpull_request.- Adds a lint that fails CI if any workflow owning a required context has an unbounded job or runs on
pull_requestwithout a concurrency group.