chore(ci): bound every runs-on job with timeout-minutes, and gate it #1376
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!1376
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/1375-timeout-minutes"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
No workflow set
timeout-minutes, so a step hanging on an external host ran to the forge's default job timeout.Closes #1375
Intended-targets: #1375
Why no final step can do this
adopter-preflight-probetask 48311 sat 643s against a normal 8–30s and FAILED having posted zero statuses — while its recording step carriesif: always()precisely so a failed grade still records a verdict.A job that is KILLED runs no
always()steps at all. A mechanism must be able to change control flow, and analways()step in a killed job has no control flow left to change. Only a timeout bounds this layer.Bounds are per class, from observed durations
One number everywhere would be the wrong shape: a timeout that fires on legitimate work is worse than none, because it trains people to re-run rather than read. Measured over 456 completed tasks — proxy
updated_at - run_started_at, which includes queue time and therefore overstates:⚠️ The release-path bound is the one I could not measure. Twelve declared job names never appeared in the observed window because they only run on a cut. 30 minutes is a generous guess disclosed as a guess rather than presented as a measurement. It is still bounded, which is the point.
🔑 The ~683s outliers are ONE event, not four.
bats,decide + act,record reviewed vs landed commitandadopter vantageeach show a single run at 643–685s against normal maxima of 30–78s. Four unrelated jobs at the same duration is a runner or queue stall — and a bound would have turned that one event into four fast, attributable reds instead of four unexplained long runs.uses:jobs are exempt, deliberatelyA reusable call takes no
timeout-minutesof its own. All twelve resolve to reusables in this tree whose ownruns-onjobs are bounded here, so bounding the job that actually runs is the only bound that can fire. The gate records the exemption count rather than silently skipping them.The gate and its arm
A bats gate refuses any
runs-onjob without a bound — and refuses two ways a gate like this goes hollow: an enumeration that selected no files, and a selector that matched files but no jobs. Per¶39it counts what the enumeration RETURNED rather than reporting "0 missing" from a sweep that graded nothing.Distinct redden-sets, each mutation verified applied against a saved original.
Also: three diagnostics narrowed
They said a non-
always()step "will be skipped on the runs that matter" — which covers a failing step and not a killed job. Handed over by @shipwright with the measurement rather than carried in a separate PR.Scope
The gate checks that a bound exists, not that any bound is the right value; it says so in its own PASS line. The runner stall itself is a separate problem and is not addressed here.
go test ./...rc=0 ·go vetrc=0 ·gitea-twin --checkrc=0 ·fragment-checkrc=0 ·register-checkrc=0 · bats 194 ok / 0 not-ok.🤖 Generated with Claude Code
https://claude.ai/code/session_01LUEggQMJjaizj2nFVofeyH
📌
#1375's four ACs were ticked with their evidence after this PR opened, andAC2's wording was corrected by @quartermaster — the file-level claim was false as written, because nine workflow files contain onlyuses:jobs and a reusable call takes notimeout-minutesof its own. The true statement is the one the gate enforces: 48runs-onjobs bounded, 0 missing, 12uses:-exempt with the exemption count recorded rather than skipped.(This edit exists to re-trigger
ac-closure-check, which fires onpull_request: [edited]and had graded the ACs before they were ticked.)release-toolkit#1375. No workflow set `timeout-minutes`, so a step hanging on an external host ran to the forge's default job timeout. WHY NO FINAL STEP CAN DO THIS. adopter-preflight-probe task 48311 sat 643s against a normal 8-30s and FAILED having posted zero statuses — while its recording step carries `if: always()` precisely so a failed grade still records a verdict. A job that is KILLED runs no always() steps at all. A mechanism must be able to change control flow, and an always() step in a killed job has no control flow left to change. Only a timeout bounds this layer. BOUNDS ARE PER CLASS AND CHOSEN FROM OBSERVED DURATIONS, not one number applied everywhere: a timeout that fires on legitimate work is worse than none, because it trains people to re-run rather than read. Measured over 456 completed tasks (proxy = updated_at - run_started_at, which includes queue time and therefore OVERSTATES): 5 min 5 jobs external-network normal 8-30s; the 643s hang is the case 10 min 22 jobs gate n=403, p99 61s, max 81s 20 min 9 jobs build/test bats + go-ci, normal max 78s 30 min 12 jobs release path NO DATA — see below ⚠️ The release-path bound is the one I could not measure. Twelve declared job names never appeared in the observed window because they only run on a cut, so 30 minutes is a generous guess disclosed as one rather than a measurement. It is still bounded, which is the point. 🔑 THE ~683s OUTLIERS ARE ONE EVENT, NOT FOUR. `bats`, `decide + act`, `record reviewed vs landed commit` and `adopter vantage` all show a single run at 643-685s against normal maxima of 30-78s. Four unrelated jobs at the same duration is a runner or queue stall, not four coincidences — and a bound would have turned that one event into four fast attributable reds. `uses:` JOBS ARE EXEMPT, DELIBERATELY. A reusable CALL takes no timeout-minutes of its own; all twelve resolve to reusables in this tree whose own runs-on jobs are bounded here. Bounding the job that actually runs is the only bound that can fire. THE GATE AND ITS ARM. A bats gate refuses any runs-on job without a bound, and refuses two ways a gate like this goes hollow: an enumeration that selected no files, and a selector that matched files but no jobs. Per CLAUDE.md ¶39 it counts what the enumeration RETURNED rather than reporting "0 missing" from a sweep that graded nothing. control both arms green M1 drop go-ci.yml's bound arm 1 reds, names go-ci.yml:go M2 gate accepts a missing key arm 2 reds, "was ACCEPTED" M3 empty-enumeration guard removed arm 2 reds ALSO NARROWS THREE DIAGNOSTICS that overstate what always() buys — they said a non-always() step "will be skipped on the runs that matter", which covers a failing step and not a killed job. Handed over by @shipwright rather than carried in its own PR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LUEggQMJjaizj2nFVofeyHAPPROVED — exact head
de572e2991(base4b8f489e7f).Fresh final read after the AC-closure contexts were rerun; the head and implementation are unchanged. The workflow census independently finds 47 workflow files, 48 jobs with bounds, zero missing bounds, and 12 reusable-call exemptions. The exemption count is emitted by the gate rather than silently discarded, and the exact three mutation arms pass: dropping go-ci's bound, accepting a missing key, and removing the empty-enumeration guard all refuse with the expected diagnostics.
The 5/10/20-minute classes are grounded in the disclosed 456-task measurements. The 30-minute release-path value has no observed cut-task data; it is explicitly disclosed as a conservative guess, not presented as a measurement. That is acceptable for this bounded fail-safe scope, while the gate intentionally checks presence rather than claiming any value is optimal.
Exact-tree verification: full Bats 194/194, go test ./..., go vet ./..., YAML parse, diff check, and gitea-twin check pass. Newest-per-context Forgejo status is 28/28 SUCCESS, including both ac-closure-check contexts.
APPROVED — exact head
de572e2991(base4b8f489e7f).Fresh final read after the AC-closure contexts were rerun; the head and implementation are unchanged. The workflow census independently finds 47 workflow files, 48
runs-onjobs with bounds, zero missing bounds, and 12uses:reusable-call exemptions. The exemption count is emitted by the gate rather than silently discarded, and the exact three mutation arms pass: dropping go-ci's bound, accepting a missing key, and removing the empty-enumeration guard all refuse with the expected diagnostics.The 5/10/20-minute classes are grounded in the disclosed 456-task measurements. The 30-minute release-path value has no observed cut-task data; it is explicitly disclosed as a conservative guess, not presented as a measurement. That is acceptable for this bounded fail-safe scope, while the gate intentionally checks presence rather than claiming any value is optimal.
Exact-tree verification: full Bats 194/194, go test ./..., go vet ./..., YAML parse, diff check, and gitea-twin check pass. Newest-per-context Forgejo status is 28/28 SUCCESS, including both ac-closure-check contexts.
Landing identity record
de572e2991e208c26d1d0515744df1d86f6b669fde572e2991e208c26d1d0515744df1d86f6b669fThis is a post-merge identity record. It does not retroactively review the landed object; it records whether the server landed the object that an official approval named.