Repository navigation
Commit dff98c1
ci(lint): every job in lint.yml carries a timeout-minutes derived from its measured duration (#21064)
Fixes #21050
Clause-②: no
## What changed
`.github/workflows/lint.yml` gains five `timeout-minutes` keys, each
under a comment carrying its measurement and the derivation rule.
Nothing else moves: no job `name:`, step, `needs:`, `if:`,
`permissions:` or `concurrency:` changes. The diff against d1f8ce8 is 62
insertions, 0 deletions, in five hunks, each directly under a job's
`runs-on:` line (two commits: 5e6d90c, then 51f3743 for the ruled patch
round).
| job id | check context (`name:`, byte-identical) | `timeout-minutes`
before | after | measured p50 / p99 / max (min), n |
|:--|:--|:--|:--|:--|
| `lint` | `Lint & Repo Gates` (required) | none, so GitHub's default
360 | **55** | 15.8 / 32.6 / 32.7, 81 |
| `typecheck-workspace` | `Type Check · workspace` | none | **30** |
13.0 / 16.0 / 16.0, 86 |
| `typecheck-consumers` | `Type Check · consumer gates` | none | **20**
| 7.7 / 10.1 / 10.1, 87 |
| `typecheck-debt` | `Type Check · debt ledger` | none | **15** | 5.8 /
7.4 / 7.5, 92 |
| `typecheck-source-gates` | `Type Check · source gates` | none | **10**
| 3.2 / 3.5 / 3.7, 94 |
| `typecheck` | `TypeScript Type Check` (required) | 10 | 10 (line
untouched) | 0.1 / 0.1 / 0.2, 85 |
Measurement (the card's, transcribed into the comments): the 100 most
recent completed `lint.yml` runs, 2026-10-01T00:23Z to 03:24Z, 96 runs
read, 525 successful job rows, job `started_at` to `completed_at` from
the jobs API. Rule: about 2x the measured maximum, rounded to a 5-minute
grain, never below 10 minutes, never above the merge queue's 60-minute
`check_response_timeout_minutes`. For `lint` the window binds rather
than 2x, and the wall sits below it, at 55, leaving 22 minutes (1.7x)
over the measured maximum. The `lint` comment carries the full
reasoning; the four lane comments are short and point at it.
## Why
`Lint & Repo Gates` is the longest required check in every merge-queue
group, and nothing in this workflow bounds a step that stops producing
output: no step runs under `scripts/run-with-stall-guard.mjs`, so the
job timeout is the only wall these jobs can have, and five of six
declared none. A silent step would hold the job `in_progress` until
GitHub's 360-minute default, while the queue (ruleset 12119582) gives up
on the entry at 60 minutes with no failing check to read and no `Merge
Queue Triage` comment, and the hung job keeps a runner for up to five
more hours.
## What GitHub does at the wall: documented, plus one reading on this
repository
No local test can drive a job timeout. GitHub documents that a job
exceeding `timeout-minutes` is cancelled. One reading on this repository
agrees, read this session from the jobs and check-runs APIs: in run
36803445860 (`CI`, `merge_group`), job `Test Core (1/6)`, stopped by its
45-minute wall, has `conclusion: cancelled` and a `failure`-level
annotation reading `The job has exceeded the maximum execution time of
45m0s`. For the four type-check lanes, a `cancelled` lane fails the
required `TypeScript Type Check` aggregate (its verdict step fails on
anything but `success`). For `lint`, `cancelled` is the conclusion of
the required context itself.
Ruleset 12119582, read this session:
`merge_queue.check_response_timeout_minutes: 60`, and its required
contexts include `Lint & Repo Gates` and `TypeScript Type Check`.
## Verification (head 51f3743, base d1f8ce8)
- **Structure.** The file parses under PyYAML 6.0.1 and js-yaml 5.4.2. A
per-job fingerprint (name, step count, SHA-256 over every key except
`timeout-minutes`, plus the top-level keys and the job order) is
identical before and after except for the five `timeout-minutes` values.
`actionlint` is not on the PATH; nothing was installed.
- **Mechanism assumption A1.** `git grep -c 'run-with-stall-guard' --
.github/workflows/lint.yml` answers 1, not 0. The hit is prose in the
`Stall-guard self-test` step's comment, and no step runs under the
guard. `pnpm check:stall-guard-budget` exits 0: 39 workflow files, 65
jobs, 730 steps, 10 guard-wrapped steps, none of them in this file.
- **Mechanism assumption A2.** `node scripts/check-ci-filter-parity.mjs`
(and its `--self-test`), `node scripts/check-required-contexts.mjs` (7
required contexts pinned across 3 workflows) and `node
scripts/ci/select-shard-timings-run.mjs --self-test` all exit 0 on this
diff.
- **Derived gates.** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack`, with no paths, derived the change
set from git against merge base d1f8ce8 and printed 44 commands. 43
exit 0, including `pnpm check:pm-dispatch-gates` (1976 cases, 680 s).
`pnpm check:type-check-debt` exits 3, PREREQUISITE NOT MET: its
`--re-measure` half needs the built package closure, so it is **NOT
MEASURED** locally, and CI runs it in the `Type Check · debt ledger`
lane. Its `--self-test` half passed, and `pnpm
check:type-check-coverage`, which reads this file, exits 0. `--ran`
reconciliation: 44 accounted, 43 run, 1 NOT-MEASURED, 0 UNRUN. 15 of the
44 are `--self-test`-only commands that judge their own script, not this
diff.
- **Named and dispatch-time extras**, all exit 0: `pnpm
check:doc-authoring`, `pnpm check:nul-bytes`, `pnpm
check:pm-skill-id-lint`, `node scripts/check-issue-citations.mjs` (no
citations added), `pnpm check:stall-guard`, `pnpm
check:pm-governed-prose`, `pnpm check:route-ledger-census`, `pnpm
check:select-shard-packages`, `node
scripts/measure-test-shard-timings.mjs --self-test`, `node
scripts/pr-labels.mjs --self-test`.
- **Prose.** The added lines carry no tracker number and no model
identifier, and no raw control bytes.
- **Patch round at 51f3743.** `pnpm check:doc-authoring`, `pnpm
check:pm-skill-id-lint`, `node scripts/check-issue-citations.mjs`, `pnpm
check:nul-bytes`, `node scripts/check-ci-filter-parity.mjs`, `node
scripts/check-required-contexts.mjs` and `pnpm check:stall-guard-budget`
all exit 0. The file parses under PyYAML and js-yaml. The structural
fingerprint against d1f8ce8 differs only in the five added
`timeout-minutes` values (55/10/30/15/20), and the aggregate's 10 is
unchanged.
## Acceptance notes
- **Floor clause (ruled).** The rule's floor is a flat 10 minutes, which
all six values meet. The earlier maximum-plus-10 wording, which four
values did not meet, was dropped from the `lint` comment on the seat's
ruling.
- **Clock ordering (ruled).** A job's wall starts when a runner picks
the job up, which is after the queue's 60-minute check-response window
has opened, so a wall equal to that window would let the queue remove a
hung entry before the check turns `cancelled`. On the seat's ruling the
`lint` wall is 55. The `cancelled` check and its annotation land before
the queue's window closes whenever pickup takes under five minutes, so
the removal is attributable to a visible failing check. 55 leaves 22
minutes (1.7x) over the measured maximum of 32.7.
- **Merge Queue Triage scope.** It subscribes to the `CI` workflow only
(`workflow_run`, `workflows: [CI]`), not `Lint & Type Check`, and fires
on `failure` only. A timed-out lint job gets no triage comment either
way. Its header scopes it to "every red merge_group CI run", so this is
its declared scope, not a defect. Noted, not filed; carrier: none.
- **Card location detail.** The card places the `Stall-guard budget
invariant` step in the `lint` job, but it sits in
`typecheck-source-gates`. That has no effect on this change.
`skip-changeset` applies: `.github/workflows/` publishes nothing, so no
changeset was created and the seat applies the label.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01PGMD6TRDaZY8Ubyo7Ukm66)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent dcd3309 commit dff98c1
1 file changed
Lines changed: 62 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
178 | 178 | | |
179 | 179 | | |
180 | 180 | | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
181 | 216 | | |
182 | 217 | | |
183 | 218 | | |
| |||
5099 | 5134 | | |
5100 | 5135 | | |
5101 | 5136 | | |
| 5137 | + | |
| 5138 | + | |
| 5139 | + | |
| 5140 | + | |
| 5141 | + | |
| 5142 | + | |
| 5143 | + | |
5102 | 5144 | | |
5103 | 5145 | | |
5104 | 5146 | | |
| |||
5885 | 5927 | | |
5886 | 5928 | | |
5887 | 5929 | | |
| 5930 | + | |
| 5931 | + | |
| 5932 | + | |
| 5933 | + | |
| 5934 | + | |
| 5935 | + | |
| 5936 | + | |
5888 | 5937 | | |
5889 | 5938 | | |
5890 | 5939 | | |
| |||
6092 | 6141 | | |
6093 | 6142 | | |
6094 | 6143 | | |
| 6144 | + | |
| 6145 | + | |
| 6146 | + | |
| 6147 | + | |
| 6148 | + | |
| 6149 | + | |
6095 | 6150 | | |
6096 | 6151 | | |
6097 | 6152 | | |
| |||
6322 | 6377 | | |
6323 | 6378 | | |
6324 | 6379 | | |
| 6380 | + | |
| 6381 | + | |
| 6382 | + | |
| 6383 | + | |
| 6384 | + | |
| 6385 | + | |
| 6386 | + | |
6325 | 6387 | | |
6326 | 6388 | | |
6327 | 6389 | | |
| |||
0 commit comments