MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-3768

(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

Done
Description

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:

workflowrunner jobsunboundedjob ids
sandbox-images.yml66sandbox-smoke · sandbox-profiles-matrix · sandbox-profiles · sandbox-published · sandbox-public · sandbox-commands
acceptance-video.yml33membership · build · acceptance
release-sandbox.yml22guard · readme
runner-image.yml11runner-image
codeql.yml11analyze
backfill-boards.yml11backfill
sandbox-staleness.yml11staleness
release-brand.yml · release-cli.yml · release-design-system.yml · release-runner-image.yml1 each4release ×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 complete unresolved 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) reports ci.yml as 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

  1. Every job in .github/workflows/*.yml that declares runs-on also declares timeout-minutes. The count moves from 14 of 33 to 33 of 33 across the twelve files.
  2. 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.
  3. No ceiling would have failed a healthy run on record. Name the slowest observed run of each lane and show the ceiling clears it. (sandbox-images legs ran 0.1–5.8 min and runner-image 1.3 min on run 33167406878⚠️ AMENDED ON THE RECORD 2026-08-28, in the run of this card (PR #2443): those two figures are FALSIFIED. On run 33167406878 both jobs were skipped by 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)"' returns Runner 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 of ci.yml / release-sandbox.yml / release-runner-image.yml where these jobs actually ran: sandbox-profiles is 2.6–16.68 min (worst: aider tier 2, run 33214411817, conclusion: success) and runner-image is 1.7–5.38 min (worst: run 33218152057). 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 of e2e's 45 — confirmed: measured 4.9–11.3 min per shard from this lane's own runs, ceilinged at 40.)
  4. tests/ci-job-timeouts.test.ts is WIDENED from ci.yml to 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. Its SCOPE header paragraph and the ci.yml ceilings block that both name this card are updated or removed in the same change.
  5. The workflows still parse and CI is green on the fix's own pull request.

Out of scope

  • ci.yml itselfMOTIR-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) and ci.yml:1156 (runner-image) are the uses: jobs whose ceilings live elsewhere.
  • tests/ci-job-timeouts.test.ts (MOTIR-3569) — the guard to widen; its leaves NO other shape of job case is what documents why a uses: job carries none.
  • MOTIR-2970 — the same class fixed at one call site, which is how the job-level gap survived.

Resolution: open.