Repository navigation
feat(release): release from CI with one command - #259
Merged
Merged
Conversation
`vp run release [<version>] [--dry-run]` checks the checkout, the npm and GitHub logins, main's rules and CI; bumps the nine packages in a release commit; runs the release gate on it; tags it; stages the tarballs the gate checked and approves them with one 2FA; pushes; checks what npm serves; writes the GitHub release from the Conventional Commits with changelogithub; and installs the release from npm into clean apps. Before publishing, a failure undoes the local commit and tag; after, a rerun picks up where it stopped. The release workflow is gone: nothing publishes from CI. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…skill `docs/internal/releasing.md` describes `vp run release` step by step: what stops it, what a rerun picks up, why it publishes before pushing, and npm's 2FA. `docs/internal/research/release-flow.md` compares how TanStack, antfu, unjs, changesets and release-it release, with npm's and pnpm's rules in October 2026, and recommends keeping the local flow. `/release` (`.claude/skills/release/SKILL.md`) is a project skill only the owner starts: it runs the command in a pseudo-terminal (`terminal.sh`), relays npm's 2FA link, then rewrites the generated notes from `notes-template.md` with `gh release edit`. AGENTS.md, IMPLEMENTATION_PLAN.md, architecture.md, quality-gates.md, release-audit.md, the README and CHANGELOG.md follow: release notes come from the Conventional Commits, so PRs add no changelog line. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This was referenced Oct 15, 2026
…arts From the review of #259: - A rerun once publishing has started (the tag on GitHub, any package on npm, or .release/release.json saying so) skips the CI checks and the gate, which passed before, instead of getting stuck on them. The gate's state records the manifest's hash, so a later dry run's tarballs never pass for the gate's. - The undo covers everything before the first upload, Ctrl-C and SIGTERM included; the confirmation moves to the start, and the terminal check to the checks. A dry run commits on a detached HEAD, so the branch never moves. - git never prompts for credentials, and the checks try a dry-run push. When main moves, the release merges into it as a PR would. A failed bump restores the manifests. - A commit titled as the release must change only the nine versions. pnpm's JSON is read past reporter lines, and a staged version the listing doesn't describe is looked up before it counts as a conflict. - CI's dispatched runs get a concurrency group of their own, so a push to main doesn't cancel the Firefox job a release waits for; no run starts when CI already failed. - The skill sends one-time-password accounts to their own terminal, and allows the run two hours. Co-Authored-By: Claude Opus 5.5 <[email protected]>
From the second review of #259: - A bump to a version npm already serves, from a release this checkout has no commit or tag for, stops before it commits. - SIGHUP restores too, as when terminal.sh's terminal goes away; a signal stops the gate first and writes nothing to a closed terminal. The undo covers the bump itself: a detached HEAD and manifests changed but not yet committed go back. - A started release whose .release/ a later dry run rewrote runs the gate again on the release commit instead of stopping; a checkout publishing one version refuses another until it's done. - npm's login is needed only while something is left to publish. Release tags are fetched without failing the run on one that differs. The push merges only when main moved, and says when main needs a linear history. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…next version once released From the third review of #259: a rerun from the tagged release commit checked out on its own, as the gate message advises after main moved on, no longer stops for not being on main; and a release counts as done once its GitHub release is out, so an install test that keeps failing doesn't hold up the next version. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The owner confirms nothing: `vp run release [<version>]` (or /release) checks the checkout and CI, picks the version, starts the release workflow on main and watches it. The workflow checks the version and npm's trusted publisher on each package, bumps and gates the release commit on a fresh runner (Firefox included), publishes the gate's tarballs with npm trusted publishing and provenance, then pushes main and the tag and writes the GitHub release, and installs from npm. Nothing leaves the runners before the gate passes, and main gets the version once npm serves it. A run that fails after it starts publishing finishes when its failed jobs run again, which the command does. Gone with it: staging and approval, npm's login and 2FA on the laptop, the version prompt, the local gate's undo and state, the Firefox wait and the terminal helper. `vp run release:trust` sets up the trusted publishers once. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…cision behind it releasing.md walks through a release from the owner's side and the workflow's, what a rerun picks up, and the owner's one-time setup: a trusted publisher on each package (vp run release:trust or npmjs.com, within 48 hours of the first release), the old token's removal, and optionally disallowing tokens. The research note adds the zero-confirmation decision, why workflow_dispatch beats a tag push, and what trusted publishing changes for pnpm's trustPolicy. The skill, AGENTS.md and the other notes follow; CI's concurrency goes back to what it was, since nothing waits for its Firefox job any more. Co-Authored-By: Claude Opus 5.5 <[email protected]>
… dry runs credential-free From the review of the CI release in #259: - vp run release looks at the version's runs before anything else, so watching, reporting and rerunning need only gh; only starting a run checks the checkout and CI, whose verdict on a release commit the workflow pushed comes from its parent. It won't start a release while another runs, and reruns the newest run that reached npm. - The finish job pushes the tag before main, so a conflict with main can be merged by hand; the runbook says how to finish by hand when only a tooling fix would help. - The check job stops a run that builds on another commit than the one checked, or on main from before a release that landed while it waited. Dry runs queue apart, and publish and finish in jobs of their own with no OIDC token and no write access. - The jobs with credentials pin their actions to commits, use no cache, and run the release's steps with node instead of Vite Task; ci.ts refuses to run outside GitHub Actions. - npm's 429 and other answers no longer read as a missing trusted publisher, and release:trust's dry run only prints what it would run. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…e bot's release commits From the review of #259: a bare vp run release finishes the packages' own version while its run isn't done, and otherwise stops when the only commits since the last tag are the release's own, so it can't publish an empty version. A commit counts as the release workflow's own only with the bot's address as well as the subject. The gate's upload is pinned to a commit like the privileged jobs' actions, and the runbook's by-hand finish fast-forwards main when it can. Co-Authored-By: Claude Opus 5.5 <[email protected]>
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 free
to 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.
One command releases the nine
@moltenshippackages, and nobody confirms anything: asking for the release is the approval.The laptop only triggers and watches. CI does the release, publishing through npm trusted publishing (OIDC) with provenance; no npm token exists, and there's no 2FA prompt, environment reviewer, npm login or version prompt.
A release, from the owner's side
vp run release(or/release).main, clean and in sync with origin;main's rules would let the workflow push..github/workflows/release.ymlonmainand watches it (gh run watch). Then it prints the GitHub release's link, or the failed jobs and the end of their logs./releasethen rewrites the generated notes withgh release edit.The workflow
One release at a time (
concurrency: release, nothing cancelled).checkid-token: write- the version is above the packages';
-
v<version>isn't on GitHub and npm doesn't have it;- the run is on
main, on the commit the command checked;- no release landed on
mainwhile the run waited;- every package trusts this workflow: it trades the OIDC token for a publish token, as
npm publishdoes, and throws the token away.gatechore(release): v<version>and tags it. Then runsrelease:gateon that commit: the full tier, Firefox included, with a strict dry run. Writes the notes with changelogithub and bundles the commit and tag for the next jobs.publishid-token: write, no install scriptsnpm publish --provenanceon the tarballs the gate checked, through trusted publishing, skipping any npm already serves.finishcontents: write, the only writermain, merging like a PR ifmainmoved, and writes the GitHub release, marked as a pre-release for an RC.installregistry:e2e).Dry runs and hardening:
publishandfinish. It runspublish-dry-runandfinish-dry-runinstead, which have no OIDC token and no write access, and queues apart from real releases.tools/release/src/ci.tswith node rather than through Vite Task.Why
workflow_dispatchand not antfu's tag push: with a tag push, the laptop pushes the bump and the tag before anything checks them, so a red gate leaves both behind. Here nothing leaves the runners until the gate has passed, andmaingets the version only once npm serves it. That matters because the docs site's registry followsmain.Resuming:
vp run release <version>after a run failed after publishing started runs that run's failed jobs again (gh run rerun --failed). That reuses the same commit and tarballs, kept 30 days, so npm never gets two builds of one version. This needs no checkout.main.vp run releasefinishes the packages' own version while its release isn't done. When the only commits since the last tag are the release's own, it stops with "Nothing to release" instead of publishing an empty version.Trust and the zero-confirmation trade-off
main;Moltenship/timeandrelease.yml;--provenance(no trusted publisher). pnpm'strustPolicy: no-downgraderanks trusted publisher + provenance above provenance alone (trustChecks.ts), so releases from this workflow aren't a downgrade. npm has no such check.Details and sources:
docs/internal/research/release-flow.md, which has a new section on this decision.The owner's one-time setup (also in
docs/internal/releasing.md)Do it within 48 hours before the first release. Since 2 Oct 2026, npm drops a trusted publisher that hasn't published within 48 hours of being created.
vp run release:trust. It runsnpm trust github <pkg> --file release.yml --repo Moltenship/time --allow-publish --yesfor each package; it needs npm 11.15 or later. The first call asks for 2FA; tick "skip 2FA for the next 5 minutes" and the other eight go through.Moltenship;time;release.yml;npmenvironment and itsNPM_TOKENsecret, and revoke the token on npmjs.com.vp run release:trust --disallow-tokensdoes it withnpm access set mfa=publish.Removed from the first version of this PR
terminal.sh;release:version,release:publish,release:publishedandrelease:githubtasks, plus the oldrelease.yml's token-based jobs.Kept
/releaseskill in the repo withdisable-model-invocation: true(rewritten for the CI flow);release-gatejob.Tests
nextStep,publishStartedand the version choice's rules;checkjob's problems;vp run ready:fullpassed locally ate90d41c1.vp run readypassed at the final commit,5354a5bb.release-gateis green one90d41c1(job) and on the final commit,5354a5bb(job).vp run release --dry-runfrom this branch:974733f9, the first design;c147cee0, with the jobs that have no credentials;5354a5bb, with the version picked by the command (1.0.0-rc.2).check: npm answered404 OIDC token exchange error - package not foundfor all nine, as expected before the setup, and the run reported it.gate: committed and tagged in the runner, and the gate passed.Review
Claude Opus 5.5 (xhigh) reviewed this redesign in rounds 5 to 7, after four rounds on the first one. It found 1 blocker, 5 should-fix and 11 nits, all fixed. The final round confirmed nothing open. One nit is left by choice: a dry run with no version, started while a real release is unfinished, picks that version and stops at
checkwith a clear message.Untested live
checkjob's exchange did reach npm, which answered404 OIDC token exchange error - package not foundfor all nine packages, as expected before the setup.mainand a GitHub release from the workflow: the dry run only proves the job can push the tag (git push --dry-run).Refs #207, #146, #47
🤖 Generated with Claude Code