Uh oh!
There was an error while loading. Please reload this page.
feat(pm): refuse a doomed pnpm run version rehearsal with a diagnosis instead of hanging - #9620
Merged
Merged
Conversation
…is instead of hanging (#9555) A local version rehearsal in an agent container never terminates: every clone descends from a shallow checkout, so each `.changeset/*.md` resolves to the parentless shallow boundary and @changesets/git deepens-and-retries in a loop with no attempt limit — against a remote that gains it nothing while exiting 0. Measured here: no remote 0.009s, local shallow origin 0.36s, `--unshallow` from that source 0.275s, all exit 0, none gaining a commit. It reads as slow progress and cost ~2.5h before diagnosis. `scripts/pm/release-rehearsal-clone.mjs` diagnoses the clone (mutating nothing) and refuses with the mechanism plus both measured remedies; `--prepare` repairs a throwaway clone (offline scaffold, tree hash asserted identical, plus the base branch `changeset status` needs) and refuses to fabricate commits in anything with a network remote. Its `--self-test` pins both directions and the wiring, and runs in lint.yml. docs/releases-maintenance.md now prescribes the rehearsal with the preflight in it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
os-steve
marked this pull request as ready for review
August 18, 2026 13:45
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#9555
A local
pnpm run versionrehearsal in an agent container does not fail — it hangs, at full CPU, with a cleangit statusand no output, which reads as slow progress. This ships the refusal that turns that into a one-second diagnosis, the repair for a throwaway clone, and the recipe that points at both.Measured before designing (the assumptions the dispatch asked me to test)
All on this container, 2026-08-18, against a real clone of
/home/user/objectstack(126 consumable changesets):getCommitsThatAddFilesover 5 real changeset paths,@changesets/git@4.0.0(what pnpm-lock resolves)git fetch --deepen=50with no remote configuredgit fetch --deepen=50, origin = the local shallow checkoutgit fetch --unshallowfrom that same shallow sourcegit fetch --unshallowfrom github.com in a throwaway clone38904fdf12780fa442792eebb75778ef79080642), clean statusSo the first assumption resolves in the worst direction: the deepen the retry loop depends on succeeds and gains nothing, exiting 0 every time, so no error is ever raised and the loop is genuinely infinite rather than slow. "Make it fail fast" is not available — nothing here fails.
Both remedies were then verified with the real package on the real tree: unshallowed clone resolves all 126 changesets in 3.9 s to their real add-commits (faithful changelog links); the scaffolded clone resolves them in 3.6 s but to one fabricated commit (placeholder links). The guard offers both and says which is which.
Reproducible on demand: yes, every time, in three lines (clone the container checkout, point
getCommitsThatAddFilesat its changesets, watch it not return).What ships
scripts/pm/release-rehearsal-clone.mjs— run with no flags it only diagnoses (mutates nothing, ~1.1 s on a full checkout) and exits 2 with the mechanism plus the remedies;--preparerepairs a throwaway clone (offline scaffold with the tree hash asserted identical and rolled back if it is not, plus the base branchchangeset statusneeds) and refuses to fabricate commits in anything with a network remote — in this container/home/user/objectstackis itself shallow, and two scaffold commits on a live branch is exactly the damage that must not be automated. Verified read-only against the shared checkout: it reports UNFIT and offersfetch --unshallow, never the scaffold.--is-shallow-repository. A shallow clone whose changesets were added after the boundary resolves on the first pass and is fine; refusing on shallowness alone would cry wolf on it. On the real container clone the split is visible: 107 of 126 parentless, 19 inside the 50-commit window.mainat all, sochangeset statusdies ingetDivergedCommit; the obvious workaround--since origin/mainthen exits 0 reporting nothing, which reads as "no packages to release". The guard reports it as its own finding with its own remedy.docs/releases-maintenance.mdgains a "Rehearsing the version pass (throwaway clone)" section immediately before "Cutting a release" — the recipe with the preflight inside it, which is where the next scheduled rehearsal's runner looks.lint.ymlruns the--self-test(31 assertions on real git fixtures): the trapped shape refuses, a healthy tree and a merely-shallow-but-fine tree pass untouched,--prepareleaves the tree hash unchanged, both refusal paths commit nothing, and the wiring is pinned so unlinking the script from the doc or from the step reddens instead of going quiet.What is deliberately NOT in this PR
The unmissable wiring — a preflight prefix on the
versionscript — would edit rootpackage.json, which is declared territory of the@changesets/cliv3 migration epic (#9465), so it is a hand-off, not something to route around via an adjacent file. The whole change is one prefix if that lane wants it:node scripts/pm/release-rehearsal-clone.mjs --check && changeset version && …, a no-op wherever history is full (every CI checkout of this repo,fetch-depth: 0). For the same reason the self-test is invoked asnode …rather than apnpm check:*alias — the alias line also lives in rootpackage.json. Both are stated in the step comment and the script header.No changeset: this ships a local tooling script, a docs section and a CI step — nothing published (
skip-changeset).Gates (run after the final commit,
a512043d6)Derived from the changed paths with
node scripts/pm/dispatch-gates.mjs scripts/pm/release-rehearsal-clone.mjs docs/releases-maintenance.md .github/workflows/lint.yml:pnpm check:nul-bytes— OK (6171 files, self-test 75 assertions)pnpm check:node-version— OK (27 setup-node steps across 24 workflows)pnpm check:required-contexts— OKpnpm check:shard-attestation— OK (self-test 92 assertions)pnpm check:workflow-status-functions— OK (24 workflows, 43 jobs)pnpm check:type-check-coverage— OKpnpm check:doc-anchors— OK (247 fragment links) ·pnpm check:doc-authoring— OK (377 files)pnpm check:pm-dispatch-gates— OK (273 cases) ·pnpm check:pm-skill-id-lint— OK ·pnpm check:ratchet-remedy-authority— OKpnpm exec eslint scripts/pm/release-rehearsal-clone.mjs— cleannode scripts/pm/release-rehearsal-clone.mjs --self-test— 31 assertions, greenpnpm check:type-check-debt— not run locally: it refuses without a built workspace closure (turbo run buildover all packages) and this diff contains no TypeScript, no package and no tsconfig. CI runs it with the closure built.Generated by Claude Code