Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -270,6 +270,37 @@ jobs:
- name: Governed-merges audit self-test
run: pnpm check:pm-governed-merges

# Release-rehearsal clone preflight self-test (#9555). A local
# `pnpm run version` rehearsal — the prescribed verification route for
# every release-machinery change — HANGS FOREVER in an agent container:
# every clone descends from a shallow checkout, so each `.changeset/*.md`
# resolves to the parentless shallow boundary, and @changesets/git answers
# a parentless add-commit by deepening and retrying in a loop with no
# attempt limit — against a remote that gains it nothing and exits 0 while
# doing so. Measured cost before diagnosis: ~2.5 h over two attempts,
# because full CPU with no output reads as slow progress, not as a hang.
# `scripts/pm/release-rehearsal-clone.mjs` is the refusal that turns that
# into a one-second diagnosis, plus the repair for a throwaway clone.
#
# What runs HERE is the self-test, not the live check: the live check
# judges whatever clone it is pointed at, and a CI checkout's depth is the
# workflow's business rather than a verdict this job can hold. The
# self-test pins BOTH directions on real git fixtures — the trapped shape
# refuses, while a healthy tree AND a merely-shallow one whose changesets
# sit after the boundary pass untouched (the predicate is the parentless
# add-commit, never `--is-shallow-repository` alone) — and it pins the
# WIRING, so unlinking the script from docs/releases-maintenance.md or
# from this step reddens instead of going quiet. Temp-dir fixtures, no
# network, ~1.7 s.
#
# Invoked as `node` rather than through a `pnpm check:*` alias: that alias
# belongs in root package.json, which is declared territory of the
# @changesets/cli v3 migration lane (#9465) while it runs. Same shape as
# check-links.yml's ADR-link step, and dispatch-gates.mjs derives gate
# families from either spelling.
- name: Release-rehearsal clone preflight self-test
run: node scripts/pm/release-rehearsal-clone.mjs --self-test

# Docs/skills authoring guard (#2035 / ADR-0059): TS code blocks in
# Markdown/MDX are not type-checked or ESLinted, so skills/ and
# content/docs/ can drift back to teaching the bare `: Page = {}` literal
Expand Down
41 changes: 41 additions & 0 deletions docs/releases-maintenance.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -214,6 +214,47 @@ node scripts/check-objectui-pin-fresh.mjs --ref v17.0.0 --json
the bump is that ratchet's only trigger), then re-source the Console section with
`scripts/objectui-range.mjs`.

## Rehearsing the version pass (throwaway clone)

Release-machinery changes are verified by replaying a real version pass in a
**throwaway clone** — never in a working checkout, because the pass consumes
`.changeset/*.md`, rewrites every `packages/*/CHANGELOG.md` and moves the
`package.json` versions. Run the preflight first; it is the difference between a
one-second diagnosis and a hang that looks like progress:

```bash
git clone /home/user/objectstack /tmp/rehearsal && cd /tmp/rehearsal
node scripts/pm/release-rehearsal-clone.mjs --prepare . # refuse, or repair
pnpm install --frozen-lockfile
pnpm run version # the rehearsal itself
```

### Why the preflight exists (#9555)

A clone taken inside an agent container is **shallow** (it descends from a
shallow checkout, and `.git/shallow` is inherited), and that breaks changesets in
two places at once — same root cause, two entry points, and neither of them says
so:

| symptom | dies in | what you actually see |
|---|---|---|
| `changeset version` never finishes | `getCommitsThatAddFiles`: every changeset resolves to the parentless shallow boundary, so it deepens and retries in a loop with no attempt limit | full CPU, no output, clean `git status` — reads as slow progress; the first attempt was allowed to run 70 minutes |
| `changeset status` cannot answer | `getDivergedCommit` (`git merge-base main HEAD`) — a clone of a container checkout has no local `main` at all | `Failed to find where HEAD diverged from "main"`, and the obvious workaround `--since origin/main` then exits 0 reporting **nothing**, which reads as "no packages to release" |

Diagnosing that cost roughly two and a half hours the first time. The script
carries the whole mechanism, the measurements and the two remedies in its
header — `git fetch --unshallow` (10.2 s, +36 MB here, and it gives changelog
links the *real* add-commits) or an offline two-commit scaffold that leaves the
tree hash byte-identical. Run with no flags it only diagnoses and mutates
nothing; `--prepare` repairs a throwaway clone and refuses to touch anything that
looks like a real checkout. Its `--self-test` runs in `lint.yml` and pins both
directions, so the refusal cannot rot into a guard that fires on everything or on
nothing.

**This is a local-rehearsal trap only.** `cut-rc.yml` and `release.yml` check out
with `fetch-depth: 0`, so a real cut has full history and never enters that
branch.

## Cutting a release

Two routes, and the choice is not a preference — an rc and a GA release need
Expand Down
Loading
Loading