(motir-core) MOTIR-3569 bounded `ci.yml` and left 19 runner jobs across ELEVEN sibling workflows on the 6-hour default — including `sandbox-images.yml` and `runner-image.yml`, which `ci.yml` itself calls
Type · chore (CI configuration) · Parent · MOTIR-1464, the epic holding this class — MOTIR-2970 (the call-site fix) and MOTIR-3569 (the ci.yml fix) · Discovered in · the run of MOTIR-3569, 2026-08-28 · Repo · motir-core
The gap, measured on origin/main at 13a30285f
MOTIR-3569 gives every runs-on job in .github/workflows/ci.yml a timeout-minutes, moving that file from 3 of 14 to 14 of 14. It is one file of twelve. The other eleven workflow files declare 19 runner jobs and not one timeout-minutes between them, so each still inherits GitHub's 360-minute default:
| workflow | runner jobs | unbounded | job ids |
|---|---|---|---|
sandbox-images.yml | 6 | 6 | sandbox-smoke · sandbox-profiles-matrix · sandbox-profiles · sandbox-published · sandbox-public · sandbox-commands |
acceptance-video.yml | 3 | 3 | membership · build · acceptance |
release-sandbox.yml | 2 | 2 | guard · readme |
runner-image.yml | 1 | 1 | runner-image |
codeql.yml | 1 | 1 | analyze |
backfill-boards.yml | 1 | 1 | backfill |
sandbox-staleness.yml | 1 | 1 | staleness |
release-brand.yml · release-cli.yml · release-design-system.yml · release-runner-image.yml | 1 each | 4 | release ×3 · version |
Derived by parsing each workflow's jobs: mapping (job-level keys at exactly four spaces, so a step's timeout-minutes cannot be miscounted as a job's); the script is in the MOTIR-3569 run's transcript and the counts are reproducible from the files.
Why this is not "the rest of the sweep" but the SAME harm, still live
ci.yml calls two of these workflows. Its sandbox and runner-image jobs are uses: calls into sandbox-images.yml and runner-image.yml, and GitHub does not accept timeout-minutes on a uses: job — the ceiling has to live in the CALLED workflow, where there is none. So seven of the nineteen sit inside a CI run, under ci-complete, which needs both callers:
- a wedge in any of them holds
CI completeunresolved for up to six hours, which is precisely the state MOTIR-3569 was filed about; - MOTIR-3569's own guard (
tests/ci-job-timeouts.test.ts) reportsci.ymlas fully bounded while that is true, because its scope is one file — it says so in its header and names this card; - and the two jobs are path-gated (
needs.changes.outputs.images), so the hole opens on exactly the PRs that touch the image inputs and is invisible on every other one.
The remaining twelve are release and scheduled lanes. Their wedge costs no PR verdict, but it is the same six hours, on lanes nobody is watching — sandbox-staleness and backfill-boards run on a schedule, so a hang there is discovered by the next person who wonders why a sweep stopped reporting.
Acceptance criteria
- Every job in
.github/workflows/*.ymlthat declaresruns-onalso declarestimeout-minutes. The count moves from 14 of 33 to 33 of 33 across the twelve files. - Each ceiling is justified in a comment naming the job's observed duration and the headroom multiple applied — the bar MOTIR-3569 set, applied to the jobs it did not reach. Read durations from the platform (
gh api repos/moooon-B-V/motir-core/actions/runs/<id>/jobs), never guessed; a lane with no recent run says so and takes a stated floor. - No ceiling would have failed a healthy run on record. Name the slowest observed run of each lane and show the ceiling clears it. (
— ⚠️ AMENDED ON THE RECORD 2026-08-28, in the run of this card (PR #2443): those two figures are FALSIFIED. On runsandbox-imageslegs ran 0.1–5.8 min andrunner-image1.3 min on run 3316740687833167406878both jobs wereskippedby their path filter —gh api repos/moooon-B-V/motir-core/actions/runs/33167406878/jobs -q '.jobs[] | select(.name|test("Sandbox|Runner image")) | "\(.name) \(.conclusion)"'returnsRunner image skipped/Sandbox images skipped— so the range measures nothing. A skipped job reports a duration of zero, not an absence, which is why it read as a plausible measurement. Re-measured across the 14 runs ofci.yml/release-sandbox.yml/release-runner-image.ymlwhere these jobs actually ran:sandbox-profilesis 2.6–16.68 min (worst:aidertier 2, run33214411817,conclusion: success) andrunner-imageis 1.7–5.38 min (worst: run33218152057). This matters rather than being pedantic: a ~3× ceiling on the card's 5.8 is 18, and 18 would have FAILED that green 16.68-minute run — the exact trade this card's own Out of scope forbids. Planning bug: MOTIR-3854.; the acceptance lane is 10–20× slower than ordinary E2E and needs its own reading, not a copy ofe2e's 45 — confirmed: measured 4.9–11.3 min per shard from this lane's own runs, ceilinged at 40.) tests/ci-job-timeouts.test.tsis WIDENED fromci.ymlto every file in.github/workflows/, still re-deriving the job list from each file rather than restating one, so a job added to any workflow without a ceiling fails the suite. ItsSCOPEheader paragraph and theci.ymlceilings block that both name this card are updated or removed in the same change.- The workflows still parse and CI is green on the fix's own pull request.
Out of scope
ci.ymlitself — MOTIR-3569 closes it. This card starts from the tree that card leaves.- Making any lane faster. A ceiling bounds a hang; it is not a performance target, and no ceiling here may be tight enough to fail a slow-but-healthy run.
Context refs
.github/workflows/— the twelve files;ci.yml:1127(sandbox) andci.yml:1156(runner-image) are theuses:jobs whose ceilings live elsewhere.tests/ci-job-timeouts.test.ts(MOTIR-3569) — the guard to widen; itsleaves NO other shape of jobcase is what documents why auses:job carries none.- MOTIR-2970 — the same class fixed at one call site, which is how the job-level gap survived.
Resolution: open.