Skip to content

[v3 migration 1/4] Rehearse @changesets/cli v3 in a throwaway clone and answer U1–U6 with measurements — no repo changes #9497

Closed

Description

@os-project-manager

Sub-issue of #9465. This card writes nothing to the repository. Its entire deliverable is measurement, because every other card in this epic is scoped by what this one finds.

Why it is separate

The epic's ruling is that the bump and the migration land as one atomic PR. That PR cannot be written honestly until someone has watched v3 actually run against this repo's shape. Guessing the migration surface from a changelog is how a release machine gets broken in a way nobody sees until release day.

Method

Work in a throwaway clone, never in a worktree that can push, and never against origin. Install @changesets/cli@3.0.0 there, enter pre mode in that clone, and drive the real flows: changeset status, pnpm run version, changeset (the authoring prompt), and a changeset version with nothing pending.

Do not enter or exit pre mode on origin. Do not publish. Do not push tags.

The six questions, each answered with a command and its output

Acceptance

If the rehearsal shows the migration is materially larger than the epic assumes, say so with the measurement — re-scoping the epic is a legitimate and useful outcome of this card.

Activity

  1. os-project-manager commented on Aug 18, 2026

    @os-project-manager
    CollaboratorAuthor

    Claim: epic #9465, round 1
    Session: session_01HDA9nN6nXQngoQUAAzRdMb
    Branch: none — and that is the point of this card. It writes nothing to the repository, so there is no branch, no worktree in the shared checkout, and no PR. The work happens in a throwaway clone that cannot push.
    Worktree: objectstack-issue-9497-clone (a disposable clone, not a linked worktree of the primary checkout)
    Domain: domain:devx
    File surface: empty by construction. git status in the real checkout must be clean at the end. If this card finds itself wanting to edit a tracked file, that is a breach — stop and report.
    Container & model: M, mode:subagent, model: opus — a judgment card, not a mechanical one: its output is six verdicts that scope the rest of the epic, and a wrong verdict here propagates into a release-machinery PR. The top tier is reserved for #9498, which actually rewrites the gates.
    Serial constraints cleared: the epic's declared territory (.changeset/**, the four changeset scripts, cut-rc.yml / release.yml / pr-automation.yml, root package.json) has no in-flight PR against it — checked against the open PR list. This card touches none of it anyway. #9217 and #9208 are the two open PRs inside that territory and both are held: #9217 superseded by #9498, #9208 sequenced behind #9499.

    Dispatch note recorded rather than left implicit: this is the first card of the epic and it is deliberately the cheapest one to be wrong about. Nothing it produces can damage the repository; everything it produces constrains what #9498 is allowed to assume.


    Generated by Claude Code

  2. os-project-manager commented on Aug 18, 2026

    @os-project-manager
    CollaboratorAuthor
    {
      "issue": 9497,
      "status": "done",
      "branch": "(none — throwaway clone, no branch, no remote: scratchpad issue-9497/clone, rebuilt non-shallow as issue-9497/repo)",
      "pr": null,
      "premise_still_valid": true,
      "summary": "Rehearsed @changesets/cli@3.0.0 against a throwaway, remote-less clone of origin/main @ 3397d7ec9, with @changesets/cli@2.31.1 installed beside it as a control for every run. All six questions answered with commands and output; the report comment is on the epic #9465 as the card requires, plus a freshness re-check against the newer origin/main (5909307fa). Zero repo changes: no branch, no commits, no PR, no pre-mode entry/exit on origin, no publish, no tag, no workflow dispatch; git status in /home/user/objectstack is clean. Two results re-scope the epic. (1) v3's exit-1 does NOT cover #4898 — an all-empty-frontmatter set exits 0 on v3 exactly as on v2 (the predicate is byte-identical; v3 only swapped `return` for `throw`), and v3's reader additionally scans .changeset/pre/, which disarms the guard for a whole RC window; check-empty-changeset.mjs stays. (2) v3 changes the computed release number: from the same stock a pre-mode cut is 18.0.0-rc.0 under v2 and 17.1.0-rc.0 under v3, traced by ablation to @objectstack/cli's single optional peerDependency on @objectstack/driver-turso plus prerelease range semantics, with no changeset declaring a major. cut-rc.yml asserts computed == dispatched, so that needs a maintainer ruling before #9498 is written. Also found and filed #9518: cut-rc.yml's unstaged assertion trips today on v2, on the three paths sync-docs-image-tags.mjs writes.",
      "tests": "No build/test — this card writes nothing; every claim is a rehearsal run. Rig: throwaway clone of origin/main @ 3397d7ec9 (tree sha dba51a9f1b49aa09d5fb70a8770f77555965af19, identical to origin/main), `git remote -v` empty; @changesets/cli@3.0.0 and @changesets/cli@2.31.1 installed outside the tree and symlinked in per run; Node v22.22.2. NOTE ON THE RIG: this box's checkout is a shallow clone (50 commits) and @changesets/git's getCommitsThatAddFiles never terminates on a shallow repo with no remote (`git fetch --deepen=50` exits 0 doing nothing, so the deepen loop spins) — two runs were killed at 2 min and 9.5 min before that was diagnosed; identical code in v2 and v3, so not a v3 regression. Every number below comes from a rebuilt non-shallow 2-commit repo carrying the identical tree. || U1 `changeset version`, real exit codes: zero changesets -> v2 exit 0 'warn No unreleased changesets found, exiting.' / v3 exit 1 'No unreleased changesets found.'; 3 all-empty-frontmatter changesets -> v2 exit 0 and v3 exit 0, both 'All files have been updated…', both deleting the three files with ZERO package.json/CHANGELOG.md modified; in pre mode with the root stock consumed and .changeset/pre/ holding 163 -> v3 exit 0 (guard disarmed). || U2 `changeset status --output`, @objectstack/spec: v2 non-pre key=true 17.1.0, key=false/absent 18.0.0; v2 pre 18.0.0-rc.0 in all three states; v3 17.1.0 / 17.1.0-rc.0 in all six. Ablation: deleting @objectstack/cli's peerDependencies block flips v2 pre from {\"major\":69,\"patch\":7,\"none\":1} to {\"minor\":69,\"patch\":7,\"none\":1}. || U3 v3 pre-mode `changeset version` git status --porcelain: 303 lines = 163 D (.changeset/*.md) + 138 M (69 package.json + 69 CHANGELOG.md) + 2 ?? (.changeset/pre.json, .changeset/pre/); pre.json itself never modified. v2 control: 153 lines = 152 M (76 + 76 + pre.json) + 1 ??, zero deletions, zero untracked. cut-rc.yml's staging + regex replayed verbatim: 301 staged, regex ACCEPTS every path (^\\.changeset/ covers .changeset/pre/). Full `pnpm run version` on BOTH versions leaves content/docs/deployment/self-hosting.mdx, content/docs/upgrading.mdx, docker/README.md unstaged -> the workflow's 'tracked files modified but unstaged' assertion exits 1 (filed as #9518). || U4 defaults: @changesets/config 3.1.4 unset -> {version:true,tag:false}; 4.0.0 unset -> {version:false,tag:false}. Measured: 7 private packages stop bumping under v3, and 2 private-only changesets (authz-matrix-scope-narrowing.md, showcase-predicate-sparse-face-remainder.md) are never consumed and survive pre enter / RC1 / RC2 / pre exit / GA version. Setting \"privatePackages\":{\"version\":true,\"tag\":false} on v3 reproduces the v2 plan exactly: {\"minor\":69,\"patch\":7,\"none\":1} with identical private versions. || U5 full RC window driven on both CLIs; per-step git status: v2 RC1 153 M / 0 D / 0 ??, v3 RC1 138 M / 163 D / 1 ??; pre exit is 1 M (pre.json) on both; GA version v2 169 D vs v3 167 D (166 under .changeset/pre/). Injected \"@objectstack/cli\": major in pre mode still yields 18.0.0-rc.1 under v3, so the exemption's premise holds. The cut commit reads to git as 163 R100 renames and the gate's own pathspec DOES reach into pre/: `git diff --name-status --diff-filter=AMR RC1^ RC1 -- '.changeset/*.md'` returns exactly 163 R. Rename-limit hazard measured and dismissed: exact renames survive `diff.renameLimit=10` (A-count 0); only --no-renames flips them to 163 A. || U6 NOT MEASURABLE here and reported as such — changesets/action cannot run outside Actions and is outside this session's repository scope, so its bundled reader was not read. What was measured: v2-era @changesets/read does a single non-recursive fs.readdir of .changeset (no pre/ branch), while the v1-of-the-package shipping with cli v3 explicitly pushes pre/ entries — so after a v3 cut the CLI counts 165 and a v2-era reader counts 2. || Freshness: re-diffed the declared territory across 3397d7ec9..5909307fa; only package.json (an unrelated check:org-identifier --self-test prefix) and objectui-changeset-digest.mjs (#9448; inPreMode still `pre?.mode === 'pre'`) moved, stock 165 -> 198, .changeset/pre.json still absent. No verdict affected. || Real checkout: `git status --porcelain` empty, still on the PM's branch, no worktree added.",
      "open_questions": [
        {
          "question": "Under v3 the next RC cut from today's stock computes 17.1.0-rc.0, where v2 computes 18.0.0-rc.0 — from identical changesets, none of which declares a major. cut-rc.yml asserts the computed version equals the dispatched one, so the first cut after #9498 lands will refuse a dispatch written from the old expectation. Is the next train 17.1.0 or 18.0.0?",
          "options": [
            "A — accept 17.1.0: treat v2's 18.0.0 as the artifact it is (one optional peerDependency on @objectstack/driver-turso plus 'a prerelease never satisfies a non-prerelease range'), and let the launch-window convention in check-changeset-no-major.mjs mean what it says — breaking changes ship as minor, majors are declared deliberately or not at all.",
            "B — preserve 18.0.0: land a deliberate major-declaring changeset (or an explicit version input) in the same PR, so the number the last several trains were built on carries forward and no downstream pin reasoning changes.",
            "C — defer: land #9498 with the migration and settle numbering at #9500, inside the next real RC window."
          ],
          "recommendation": "A, with the ruling recorded on #9465 before #9498 is written. Real business need: nothing downstream consumes the major digit as a promise — v2's 18.0.0 was never anyone's decision, it was one optional peer edge that no author could see, so 'preserving' it preserves an accident. Long-term soundness: the repo already has an explicit, enforced convention (check-changeset-no-major.mjs) saying a major is declared, not inferred; keeping B means the number stays a function of a dependency-graph detail, which is exactly the coupling this epic exists to remove. Making AI-written code hard to get wrong: A makes 'what version does this cut produce' derivable from the changesets an author can read, instead of from a peer range interacting with prerelease semver — B leaves the trap armed and undocumented. C is the weakest: it defers a decision that changes what #9498's own tests should assert, and #9500 cannot run until a real window opens."
        },
        {
          "question": "U6 could not be measured: changesets/action is a compiled Action, unrunnable outside GitHub Actions, and changesets/action is outside this session's repository scope so its bundled @changesets/read was not read either. The decisive fact is measured on the libraries — after a v3 cut the CLI sees the full stock while a v2-era non-recursive reader sees only the unconsumed residue — but which generation each action version bundles is unanswered.",
          "options": [
            "A — #9499 answers it by reading changesets/action's source for both v1 and v2 (needs that repo in scope, or a maintainer paste of the relevant branch table and reader).",
            "B — answer it empirically after #9498 lands, by watching whether the 'chore: version packages' PR still refreshes during the next RC window (#9500).",
            "C — treat it as low risk and proceed, since release.yml's lane carries no publish: script."
          ],
          "recommendation": "A. It is cheap, it is exactly what #9499 was carved out to do, and B pays for the answer with a live RC window — the one place this repo has already been burned by silent green (#4898). C misreads the risk: #6170's no-publish guarantee is untouched, but the failure mode here is the version PR quietly ceasing to refresh mid-window, which looks like nothing at all until a cut is dispatched against a stale number."
        }
      ],
      "out_of_scope_findings": [
        "filed as #9518: cut-rc.yml's 'nothing tracked left unstaged' assertion trips on the three paths sync-docs-image-tags.mjs writes (content/docs/deployment/self-hosting.mdx, content/docs/upgrading.mdx, docker/README.md) — measured on today's @changesets/cli@2.31.1, so it blocks the next cut regardless of this migration; introduced when #9064 added the rewriter to the `version` script after the last cut",
        "not filed, folded into the #9498 scope on #9465 instead (all four are inside the epic's declared territory, and filing them separately would put a second PR in the same files): check-changeset-no-major.mjs's header claims its two siblings use --diff-filter=AM when both use AMR today; check-adr-0087-registration.mjs's changesetDirAt uses a RECURSIVE `git ls-tree -r`, so .changeset/pre/*.md will enter its --list and --audit-stock output under v3; cut-rc.yml:792 and docs/releases-maintenance.md:306 both assert consumed changesets are 'recorded in .changeset/pre.json', which v3 falsifies",
        "no action taken, disposition noted on #9465: #9450 (cut-rc.yml:285's 'range is walkable' preflight, raised by the domain:devx lane) sits in this epic's declared territory but is a disjoint block from #9518 and was not exercised by this rehearsal — flagged so #9498 decides up front whether it carries one, both or neither"
      ]
    }

    Generated by Claude Code

  3. os-project-manager commented on Aug 18, 2026

    @os-project-manager
    CollaboratorAuthor

    ACCEPT — closing. Verified against what this card promised, not against its own summary.

    • The deliverable is where it belongs: the six verdicts, each with its command and output, are on the epic (Migrate the release toolchain to @changesets/cli v3 — one atomic PR carrying the bump, the pre-mode restructure, and the gates that model v2's semantics #9465), not on this card.
    • The file surface was honoured, which for this card was the whole discipline: no branch, no commits, no PR, and the real checkout left clean. That mattered more here than usual — the rehearsal had to touch a release pipeline without touching the repository.
    • U1 and U3 were both answered by construction rather than by reading, which is what they were written to force. U1 in particular came back against the epic's stated benefit, and was reported that way rather than shaded toward the answer the card was hoping for. That is the most valuable thing this run produced.
    • U6 is reported as not measurable here, with the reason (a compiled Action, and changesets/action outside this session's repository scope) and with the decisive library-level fact measured anyway. An honest "could not measure" beats a confident guess; [v3 migration 3/4] Decide #9208 — changesets/action v1 → v2 against release.yml's pinned rationale #9499 inherits it.
    • Two rig hazards were surfaced instead of hidden: the shallow-clone hang in @changesets/git's deepen loop (identical in v2 and v3, so not a v3 regression), and the orphaned child processes from the killed runs, tracked down and terminated.

    Downstream, already actioned: scope for #9498 is recorded on #9465 · the version-number question was escalated and is now ruled (17.1.0) · #9518 was adopted as 5/5 of the epic, graded p0 and dispatched · #9450's disposition is left to #9498 to decide explicitly rather than folded in blind.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions