diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index 2dce269cdaa..fd75bd9ccae 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -20,16 +20,56 @@ name: Check Links # "the anchors are fine". The fragment half is owned by `pnpm check:doc-anchors` # in `lint.yml`, which is REQUIRED where this lane is advisory; `lychee.toml`'s # `include_fragments` comment records why the enum is not the fix. +# +# `paths:` is what the two checks read: lychee's input globs in `LYCHEE_ARGS` +# below (taken as whole `content/` and `docs/` trees, so an image or page a +# link points at inside them counts too), its config, and the ADR step's +# `docs/adr/**` and script. Measured on the last 49 landings of `main` +# (6390fe52): 10 touched one of these, so the other 39 would not have run it. +# ⚠️ The filter is blind to the TARGET side outside those trees: a PR that +# only renames or deletes a file a doc links to (a `packages/*` directory +# from CONTRIBUTING.md, `LICENSE` from LICENSING.md) skips this lane, and the +# break shows on the next PR that touches a doc, or on a `workflow_dispatch` +# run. Keep this list equal to `LYCHEE_ARGS`. ⛔ Promoting the lane into the +# required set removes this filter in the same change: a required check +# skipped by `paths:` never reports, the #6121 stall above. on: workflow_dispatch: pull_request: branches: - main + paths: + - 'content/**' + - 'docs/**' + - 'README.md' + - 'ARCHITECTURE.md' + - 'CHANGELOG.md' + - 'RELEASE_NOTES.md' + - 'CONTRIBUTING.md' + - 'ROADMAP.md' + - 'LICENSING.md' + - 'CODE_OF_CONDUCT.md' + - 'lychee.toml' + - '.lycheeignore' + - 'scripts/check-adr-links.mjs' + - 'scripts/invoked-as.mjs' + - '.github/workflows/check-links.yml' + +# A newer run for the same pull request supersedes this one, so the older run is +# cancelled. The group key is ci.yml's shape; `cancel-in-progress` is true only on +# `pull_request`, so no other event's run is ever cancelled. +concurrency: + group: check-links-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.after || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: link-checker: name: Check Documentation Links runs-on: ubuntu-latest + # Run wall over the 100 most recent completed runs to 2026-10-27: p99 0.8 min, max + # 1.0. The bound covers the lychee retry path (two binary downloads and the 15 s wait) + # with headroom. + timeout-minutes: 10 # Least privilege: the job only reads the tree and runs lychee offline. # There is no issue-filing step, and `--offline` makes zero network # requests, so neither `issues: write` nor a GITHUB_TOKEN is needed. diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d1afaf14ce8..df682f3ea52 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -27,6 +27,9 @@ jobs: analyze: name: Analyze runs-on: ubuntu-latest + # Run wall of the 22 analyses that completed (success) in the last 100 runs, + # 2026-10-24..26: p99 59.0 min, max 63.1. The bound is that p99 plus about half again. + timeout-minutes: 90 permissions: actions: read contents: read diff --git a/.github/workflows/cross-repo-issue-closer.yml b/.github/workflows/cross-repo-issue-closer.yml index d6a4c4a5137..d34cefbb1b1 100644 --- a/.github/workflows/cross-repo-issue-closer.yml +++ b/.github/workflows/cross-repo-issue-closer.yml @@ -124,6 +124,9 @@ jobs: name: Close issues referenced in other repositories if: github.event.pull_request.merged == true runs-on: ubuntu-latest + # Run wall over the 100 most recent completed runs to 2026-10-27: p99 0.2 min, max + # 0.3. + timeout-minutes: 5 steps: - name: Close (or report) cross-repo closing keywords uses: actions/github-script@v9 diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 63579d902d8..791e15d96a9 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -36,6 +36,11 @@ jobs: publish: name: Build & push ghcr.io/objectstack-ai/objectstack runs-on: ubuntu-latest + # Job wall, all four readings on record: 3.5 and 3.5 min (the two dispatch runs), 4.3 + # min (18.1.0, Release run 40078805767, attempt 2) and 1.4 min (attempt 1, red). Four + # readings make p99 the max, 4.3; the bound is wide because the image is built for two + # platforms, arm64 under QEMU, and a cold layer cache is unmeasured. + timeout-minutes: 30 permissions: contents: read packages: write diff --git a/.github/workflows/docs-drift-check.yml b/.github/workflows/docs-drift-check.yml index f050c7c2394..804813f1e83 100644 --- a/.github/workflows/docs-drift-check.yml +++ b/.github/workflows/docs-drift-check.yml @@ -72,10 +72,20 @@ permissions: contents: read pull-requests: write +# A newer run for the same pull request supersedes this one, so the older run is +# cancelled. The group key is ci.yml's shape; `cancel-in-progress` is true only on +# `pull_request`, so no other event's run is ever cancelled. +concurrency: + group: docs-drift-check-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.after || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: docs-drift: name: Flag docs affected by code changes runs-on: ubuntu-latest + # Run wall over the 100 most recent completed runs to 2026-10-27: p99 2.1 min, max + # 6.2. + timeout-minutes: 15 steps: - name: Checkout repository uses: actions/checkout@v7 diff --git a/.github/workflows/duplicate-fix-guard.yml b/.github/workflows/duplicate-fix-guard.yml index 9d604938d48..4fffeb5e806 100644 --- a/.github/workflows/duplicate-fix-guard.yml +++ b/.github/workflows/duplicate-fix-guard.yml @@ -27,10 +27,20 @@ on: permissions: pull-requests: read +# A newer run for the same pull request supersedes this one, so the older run is +# cancelled. The group key is ci.yml's shape; `cancel-in-progress` is true only on +# `pull_request`, so no other event's run is ever cancelled. +concurrency: + group: duplicate-fix-guard-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.after || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: duplicate-fix-guard: name: No other open PR may claim the same issue runs-on: ubuntu-latest + # Run wall over the 100 most recent completed runs to 2026-10-27: p99 0.3 min, max + # 0.8. + timeout-minutes: 5 steps: - name: Check declared issues against other open PRs uses: actions/github-script@v9 diff --git a/.github/workflows/engine-split-metric.yml b/.github/workflows/engine-split-metric.yml index 63a55eb4c76..3c800a9eaea 100644 --- a/.github/workflows/engine-split-metric.yml +++ b/.github/workflows/engine-split-metric.yml @@ -18,6 +18,14 @@ on: permissions: contents: read +# A newer run for the same pull request supersedes this one, so the older run is +# cancelled. The group key is ci.yml's shape; `cancel-in-progress` is true only on +# `pull_request`, so no other event's run is ever cancelled. The schedule and dispatch +# runs share a group keyed on their own event and wait for each other instead. +concurrency: + group: engine-split-metric-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.after || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: ratio: name: Engine cross-package commit ratio diff --git a/.github/workflows/os-create-smoke.yml b/.github/workflows/os-create-smoke.yml index 9dbebd34d8d..062bf2bfbe0 100644 --- a/.github/workflows/os-create-smoke.yml +++ b/.github/workflows/os-create-smoke.yml @@ -66,6 +66,14 @@ on: permissions: contents: read +# A newer run for the same pull request supersedes this one, so the older run is +# cancelled. The group key is ci.yml's shape; `cancel-in-progress` is true only on +# `pull_request`, so no other event's run is ever cancelled. The schedule and dispatch +# runs share a group keyed on their own event and wait for each other instead. +concurrency: + group: os-create-smoke-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.after || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: create-scaffold-smoke: name: Scaffold outside the monorepo, install, build diff --git a/.github/workflows/pack-smoke-optin.yml b/.github/workflows/pack-smoke-optin.yml index 7fc687e686f..6cb53634df0 100644 --- a/.github/workflows/pack-smoke-optin.yml +++ b/.github/workflows/pack-smoke-optin.yml @@ -88,6 +88,15 @@ on: permissions: contents: read +# A newer run for the same pull request supersedes this one, so the older run is +# cancelled. The group key is ci.yml's shape; `cancel-in-progress` is true only on +# `pull_request`, so no other event's run is ever cancelled. An unrelated label's run +# gets a group of its own (its job is skipped anyway), so adding some other label never +# cancels a running `needs:pack-smoke` run. +concurrency: + group: pack-smoke-optin-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.after || github.ref }}-${{ github.event.action == 'labeled' && github.event.label.name != 'needs:pack-smoke' && github.run_id || 'run' }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: pack-smoke: # ~45 minutes of build + pack + clean install. Opt-in only — see the header. diff --git a/.github/workflows/partof-closing-keyword-guard.yml b/.github/workflows/partof-closing-keyword-guard.yml index 1be4a95bd21..8567a86f160 100644 --- a/.github/workflows/partof-closing-keyword-guard.yml +++ b/.github/workflows/partof-closing-keyword-guard.yml @@ -50,6 +50,9 @@ jobs: partof-closing-keyword: name: Part-of PR must not also close its card runs-on: ubuntu-latest + # Run wall over the 100 most recent completed runs to 2026-10-27: p99 0.8 min, max + # 0.8. + timeout-minutes: 5 steps: - name: Checkout repository uses: actions/checkout@v7 diff --git a/.github/workflows/pr-automation.yml b/.github/workflows/pr-automation.yml index 3e235ae0d21..7d355b2ad95 100644 --- a/.github/workflows/pr-automation.yml +++ b/.github/workflows/pr-automation.yml @@ -42,6 +42,16 @@ on: pull_request: types: [opened, synchronize, reopened, labeled, unlabeled, edited] +# A newer run for the same pull request supersedes this one, so the older run is +# cancelled. The group key is ci.yml's shape; `cancel-in-progress` is true only on +# `pull_request`, so no other event's run is ever cancelled. `labeled` / `unlabeled` / +# `edited` runs (Check Changeset only) and push runs (all three jobs) are kept in +# separate groups: a label written at PR creation fires right behind `opened` and must +# not cancel that run's size and label jobs. +concurrency: + group: pr-automation-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.after || github.ref }}-${{ contains(fromJSON('["labeled","unlabeled","edited"]'), github.event.action) && 'meta' || 'code' }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: # =========================================================================== # LABEL WRITES IN THIS FILE ARE ADDITIVE. Nothing here may issue @@ -143,6 +153,9 @@ jobs: && github.event.action != 'unlabeled' && github.event.action != 'edited' runs-on: ubuntu-latest + # Run wall (all three jobs) over the 100 most recent completed runs to 2026-10-27: p99 + # 2.4 min, max 2.5. + timeout-minutes: 10 permissions: # `contents: read` is for the checkout the label writer needs. Declaring # any `permissions:` block drops every scope not listed, so it has to be @@ -214,6 +227,9 @@ jobs: && github.event.action != 'unlabeled' && github.event.action != 'edited' runs-on: ubuntu-latest + # Run wall (all three jobs) over the 100 most recent completed runs to 2026-10-27: p99 + # 2.4 min, max 2.5. + timeout-minutes: 10 permissions: contents: read pull-requests: write @@ -243,6 +259,9 @@ jobs: changeset-check: name: Check Changeset runs-on: ubuntu-latest + # Run wall (all three jobs) over the 100 most recent completed runs to 2026-10-27: p99 + # 2.4 min, max 2.5. + timeout-minutes: 10 # Two exemptions, both meaning "this PR declares no release of its own": # - the `skip-changeset` label — the author's explicit opt-out; # - the Changesets release PR (`changeset-release/main`, pushed by diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 18627930446..5cbdc5a7321 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -270,6 +270,10 @@ jobs: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.refresh_version_pr) runs-on: ubuntu-latest + # Run wall of the 23 schedule and dispatch runs among the 400 most recent + # (2026-10-22..27), the only events this job runs on: p99 2.4 min, max 2.4. The bound + # leaves room for a cold pnpm store on the install step. + timeout-minutes: 20 # Serialise against itself so two refreshes cannot race the force-push to # `changeset-release/main`; never cancel in progress. The races it covers # changed with the trigger (#11233) but did not go away: a scheduled tick can @@ -978,6 +982,10 @@ jobs: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && !inputs.refresh_version_pr) runs-on: ubuntu-latest + # Run wall of the 376 push runs among the 400 most recent (2026-10-22..27), an upper + # bound for this job: p99 4.6 min, max 6.9. The bound leaves room for the backfill + # path (install plus Release writes), which none of them took. + timeout-minutes: 20 # Serialised: its backfills create GitHub Releases and push a runtime image, # and two runs doing that at once is not a state worth reasoning about. # @@ -1474,6 +1482,10 @@ jobs: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && !inputs.refresh_version_pr) runs-on: ubuntu-latest + # Run wall of the 376 push runs among the 400 most recent (2026-10-22..27), an upper + # bound for this job: p99 4.6 min, max 6.9; its own job reading on 18.1.0 (run + # 40078805767) is 1.0 min. + timeout-minutes: 10 permissions: # `actions: write` is the cancel (POST .../runs/{id}/cancel); it also # covers the reads (the waiting-runs list, the runs at each version @@ -1620,6 +1632,14 @@ jobs: (github.event_name == 'workflow_dispatch' && inputs.force && !inputs.refresh_version_pr)) runs-on: ubuntu-latest + # ⛔ A timeout here cancels mid-`changeset publish`, the half-published state the + # concurrency note below exists to prevent, so this bound is set from the steps' own + # ceilings, not from the one reading: 18.1.0's job (run 40078805767) took 32.6 min, + # the publish step 15.5 of it. Ceilings: build + Console build about 15 min cold, + # `release-verify-npm.mjs` up to 35 min after `changeset publish`, the dist-tags + # step's own 15 min; about 80 min in all, plus half again. The approval wait at the + # `release` environment is not job time. + timeout-minutes: 120 # One publish at a time per ref, and never cancelled — a run cancelled # mid-`changeset publish` is the state that leaves a fixed group half on npm. # diff --git a/.github/workflows/scaffold-e2e.yml b/.github/workflows/scaffold-e2e.yml index 8f24a997a50..765150291b0 100644 --- a/.github/workflows/scaffold-e2e.yml +++ b/.github/workflows/scaffold-e2e.yml @@ -37,6 +37,14 @@ on: permissions: contents: read +# A newer run for the same pull request supersedes this one, so the older run is +# cancelled. The group key is ci.yml's shape; `cancel-in-progress` is true only on +# `pull_request`, so no other event's run is ever cancelled. The schedule and dispatch +# runs share a group keyed on their own event and wait for each other instead. +concurrency: + group: scaffold-e2e-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.after || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: scaffold-local: name: Scaffold with repo dist diff --git a/.github/workflows/single-claim-path-guard.yml b/.github/workflows/single-claim-path-guard.yml index 61c1bfc5a88..fc74e254347 100644 --- a/.github/workflows/single-claim-path-guard.yml +++ b/.github/workflows/single-claim-path-guard.yml @@ -68,6 +68,9 @@ jobs: single-claim-path: name: No other open PR may claim the same single-writer path runs-on: ubuntu-latest + # Run wall over the 100 most recent completed runs to 2026-10-27: p99 0.8 min, max + # 0.9. + timeout-minutes: 5 steps: - name: Checkout repository uses: actions/checkout@v7 diff --git a/.github/workflows/spec-liveness-check.yml b/.github/workflows/spec-liveness-check.yml index a8a2251c8e5..787b1157667 100644 --- a/.github/workflows/spec-liveness-check.yml +++ b/.github/workflows/spec-liveness-check.yml @@ -32,10 +32,21 @@ on: permissions: contents: read +# A newer run for the same pull request supersedes this one, so the older run is +# cancelled. The group key is ci.yml's shape; `cancel-in-progress` is true only on +# `pull_request`, so no other event's run is ever cancelled. A `merge_group` run is +# never cancelled, and its key (the queue ref) is unique per queue entry besides. +concurrency: + group: spec-liveness-check-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.after || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: liveness: name: Spec property liveness runs-on: ubuntu-latest + # Run wall over the 100 most recent completed runs to 2026-10-27 (22 PR, 78 merge- + # queue): p99 1.3 min, max 1.4. + timeout-minutes: 10 steps: - name: Checkout repository uses: actions/checkout@v7 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 3003198149d..78db774fd74 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -10,6 +10,10 @@ jobs: stale: name: Clean up stale issues and PRs runs-on: ubuntu-latest + # Run wall over the 100 most recent completed runs to 2026-10-27 (2026-07-19..10-26): + # p99 0.6 min, but one green run took 26.6 min (2026-08-30). The bound keeps that run + # inside it. + timeout-minutes: 45 permissions: issues: write pull-requests: write diff --git a/.github/workflows/upgrade-harness-optin.yml b/.github/workflows/upgrade-harness-optin.yml index 921469dc9c9..35d015e3491 100644 --- a/.github/workflows/upgrade-harness-optin.yml +++ b/.github/workflows/upgrade-harness-optin.yml @@ -58,6 +58,15 @@ on: permissions: contents: read +# A newer run for the same pull request supersedes this one, so the older run is +# cancelled. The group key is ci.yml's shape; `cancel-in-progress` is true only on +# `pull_request`, so no other event's run is ever cancelled. An unrelated label's run +# gets a group of its own (its job is skipped anyway), so adding some other label never +# cancels a running `needs:upgrade-harness` run. +concurrency: + group: upgrade-harness-optin-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.after || github.ref }}-${{ github.event.action == 'labeled' && github.event.label.name != 'needs:upgrade-harness' && github.run_id || 'run' }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: upgrade-harness: name: 18.x upgrade harness (opt-in) diff --git a/.github/workflows/validate-deps.yml b/.github/workflows/validate-deps.yml index 308422b6a0d..5fc86aab793 100644 --- a/.github/workflows/validate-deps.yml +++ b/.github/workflows/validate-deps.yml @@ -31,6 +31,14 @@ on: - cron: '0 3 * * *' workflow_dispatch: +# A newer run for the same pull request supersedes this one, so the older run is +# cancelled. The group key is ci.yml's shape; `cancel-in-progress` is true only on +# `pull_request`, so no other event's run is ever cancelled. The schedule and dispatch +# runs share a group keyed on their own event and wait for each other instead. +concurrency: + group: validate-deps-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.after || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: validate: name: Validate Package Dependencies