Skip to content

Commit b85bfed

Browse files
docs(releases-maintenance): wait for the newest in-flight scheduled CI before approving release (#25320)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D3HAzRn9UnnjnvWeBJbjUjJ)_ Fixes #24889 Clause-②: no Metadata-protocol: none ## What `docs/releases-maintenance.md`, section "Cutting a GA release — the Version Packages PR flow": one maintainer step before the `release` environment approval, after the existing "First, check #4935 is current" step. - Read the Release run's `Full battery green at the version commit` job (step *Read the full battery at or before the version commit*): one line per workflow, naming the chosen run and its distance from the version commit. - If a `CI` run on the version commit, or on a commit between it and the one the `CI` line names, is still in progress (normally the hourly `schedule` run), wait for it to complete (about 25–30 minutes) before approving. Green: approve. Red: treat it as the gate's refusal. - Why: 18.0.0 was approved on a green gate that read a run 9 commits back while the run 2 commits back was in flight, and that run went red. - What it is not: a merge freeze. The snapshot flow's choice against coordinated freezes and `annotate-shipped`'s handling of a Version Packages PR merged behind `main` are referenced, not changed. Docs only, 32 added lines, one file. No workflow, script, ADR or `content/docs/releases/**` change, so no changeset (`skip-changeset`). ## Measurements (all against `origin/main` at `9dc1f42d50`) - **Gate job name:** `.github/workflows/release.yml:1508` reads `name: Full battery green at the version commit` (job id `full-battery`, step `Read the full battery at or before the version commit` at `:1556`). It runs `scripts/ci/scheduled-full-run.mjs --release-gate`, whose HALF 3 selection rule reads only `completed` `schedule` or `push` runs of `CI`, `Lint & Type Check` and `Spec Shape Gate` and "never waits". It prints its lines to the job log, not to the step summary. - **Scheduled CI cadence:** `.github/workflows/ci.yml:58` reads `- cron: '0 * * * *'` (hourly, minute 0). `lint.yml:47` carries the same cron. - **Scheduled CI duration:** the 20 most recent completed `schedule` runs of `ci.yml` on `main`, as of 2026-10-23 00:20Z (`run_started_at` to `updated_at`): 14.4–38.7 min, median 28.7, 14 of 20 in 25–31. `lint.yml` and `spec-shape-gate.yml` scheduled runs in the same window were about 10–24 min. - **18.0.0 facts, re-read:** run 39412016980 (`CI`, schedule) on `abd5acdaf7`, success, 11:01–11:25Z; run 39419131283 (`CI`, schedule) on `81120caac8`, failure, 12:01–12:30Z; run 39427630088 (`Release`, workflow_dispatch) failure. `git log --first-parent 06cab48` puts `81120caac8` 2 commits back and `abd5acdaf7` 9 commits back. ## Gates Derived by `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `64ff9db3c5`. Exit codes were captured before any pipe and reconciled with `--ran`: "13 derived, 13 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero — all 13 recorded an exit code and none of them is 3)". | Command | Exit | |---|---| | `node scripts/check-ci-filter-parity.mjs` | 0 | | `node scripts/check-closing-keyword-parity.mjs` | 0 | | `node scripts/check-closing-keyword-parity.mjs --self-test` | 0 | | `node scripts/check-comment-mask-corpus.mjs` | 0 | | `pnpm --filter @objectstack/lint run check:doc-formula-expressions` | 0 (first run exit 3, a prerequisite: `@objectstack/lint` unbuilt; built `@objectstack/formula` and `@objectstack/lint`, then re-ran) | | `pnpm check:cross-package-test-inputs` | 0 | | `pnpm check:doc-authoring` | 0 | | `pnpm check:driver-memory-census` | 0 | | `pnpm check:gitlink-declared` | 0 | | `pnpm check:nul-bytes` | 0 | | `pnpm check:refd-timer-probe` | 0 | | `pnpm check:required-contexts` | 0 | | `pnpm check:watch-hint-literal` | 0 | CI runs the rest of the families on this PR. ## Acceptance notes - The card names a scheduled `CI` run. The gate reads `push` runs on `main` too, and two more workflows, so the step is written for any in-flight `CI` run between the gate's chosen run and the version commit (normally the hourly one). It adds one sentence that the same wait applies to `Lint & Type Check` and `Spec Shape Gate`, whose scheduled runs are shorter. - The card's `:297` anchor was stale. The GA section starts at `:445` on `9dc1f42d50`. - The "Red: do not approve" branch points at the recovery already written in the `full-battery` job header of `release.yml`. It adds no new recovery procedure. - Not touched: the #7447 snapshot section and `annotate-shipped`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01LKHDniRQSWjRJyDtacwpEd Co-authored-by: Claude <noreply@anthropic.com>
1 parent 453ede3 commit b85bfed

1 file changed

Lines changed: 32 additions & 0 deletions

File tree

‎docs/releases-maintenance.md‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,38 @@ Wait for the refreshed PR's CI, then merge it. That merge is still the decision
469469
release, and the `release` environment approval is still the authorisation — neither
470470
is changed by where the refresh came from.
471471

472+
**Then, before approving, check that no nearer `CI` run is still in flight** (#24889).
473+
Open the Release run's **`Full battery green at the version commit`** job, step *Read
474+
the full battery at or before the version commit*. It prints one line per workflow it
475+
reads (`CI`, `Lint & Type Check`, `Spec Shape Gate`), each naming the run it chose and
476+
how many first-parent commits before the version commit that run's head sits. The
477+
gate reads only *completed* runs and never waits (by design: `selectFullBatteryRun`
478+
in `scripts/ci/scheduled-full-run.mjs`), so a run still in progress is invisible to
479+
it. Now open **Actions → CI** on `main`: if a run on the version commit, or on any
480+
commit between it and the one the `CI` line names, is still in progress — normally
481+
the hourly `schedule` run (`cron: '0 * * * *'` in `ci.yml`), occasionally a `push`
482+
run — **wait for it to complete before approving**. That is about 25–30 minutes from
483+
the top of the hour (the 20 hourly runs up to 2026-10-23 00:20Z took 14–39 minutes,
484+
median 28.7, 14 of them 25–31). Green: approve. Red: do not approve. It is the reading the gate would
485+
have refused had it been complete, so treat it as that refusal (recovery: the
486+
`full-battery` job's header in `release.yml`). `CI` is the long battery, so it is the
487+
window that matters in practice; an in-flight `Lint & Type Check` or `Spec Shape
488+
Gate` run nearer than its line's run deserves the same wait.
489+
490+
Why: 18.0.0 (version commit `06cab4849a`) was approved on a green gate that had read
491+
scheduled `CI` run 39412016980 on `abd5acdaf7`, **9** first-parent commits back,
492+
because the nearer scheduled run 39419131283 on `81120caac8`, 2 commits back, was
493+
still running. That run concluded **failure** at 12:30Z, minutes after the gate read
494+
(#24879, a stale dogfood fixture), and a later `force: true` dispatch that could see
495+
it was refused. The gate was right about everything it could see; the gap is only
496+
the window in which a nearer run is in flight, and only a human can wait it out.
497+
498+
**This is a wait, not a merge freeze.** `main` keeps moving the whole time: the
499+
approval publishes the version commit's tree and nothing that landed after it, so
500+
runs on later commits are not what you are waiting for. The snapshot flow above
501+
chose against coordinated freezes, and a Version Packages PR merged behind `main` is
502+
already handled by `annotate-shipped` (#23818); neither changes.
503+
472504
**After a stable `@objectstack/spec` publish, refresh the retired-after census** (#20390): run `pnpm --filter @objectstack/spec exec tsx scripts/build-retired-after-census.ts` (prefix `NODE_USE_ENV_PROXY=1` behind a proxy) and commit the rewritten `packages/spec/src/conversions/retired-after.census.json` in an ordinary PR — until it lands, the census test holds an unpublished entry's `retiredAfter` only to the range from the last censused release to the label, not to the label exactly.
473505

474506
## Drift guard

0 commit comments

Comments
 (0)