Files
improvise/RELEASING.md
T
Edward Langley 50d93eb299 docs: a release procedure, and the overreach the tag history corrects
THE ROOT CAUSE WAS A MISSING DOCUMENT, NOT A MISSING CHECK. Nothing in this repo
said "bump the version before you tag": release.yml is cargo-dist's autogenerated
mechanism, README's Installation section describes the result, and plan.md
Phase 3 carries one-time SETUP instructions from the launch plan. So the
preflight added in the previous commits is the second half of the fix. RELEASING.md
is the first, and it is a checklist rather than an explanation -- the reasoning
stays in context/code/tooling.md section 4, because a checklist followed at 2am
is not read for its argument and one carrying it does not get followed. Its own
file rather than a README section because README Installation addresses someone
INSTALLING and this addresses the one person PUBLISHING.

THE DIAGNOSIS WAS UNDERSTATED AND IS NOW CORRECTED AT THE SOURCE. The earlier
passage rested on a local `dist plan` run, which is the weaker half. v0.1.0-rc1
and v0.1.0-rc2 both carry COMPLETE published GitHub Releases from 2026-04-11 --
all four targets, .sha256 files, sha256.sum, dist-manifest.json, source.tar.gz
and improvise-installer.sh. The workflow is not merely expected to work; it
demonstrably ran end to end the last time a tag and the version agreed.

Nearly the opposite was concluded from an empty `gh run list`: Actions logs age
out at 90 days and those tags are five months old. An empty log is a statement
about RETENTION, not about history -- the artefact a run published outlives the
run, so ask the releases before asking the runs.

AND THE MISTAKE IS RECURRENT, which is the real argument for writing a procedure
down. `gh run list --workflow=release.yml --event=push` keeps the whole tag
history: rc1 FAILED TWICE (14s each) before succeeding on the third push
(3m25s), rc2 succeeded first time (3m9s), rc3 failed (21s). Three occurrences
across two releases, all the same version/tag disagreement. A one-off justifies
a note; a third justifies a document and a check before the irreversible step.

The failure signature is recorded because it is cheap to read and easy to
misread: `plan` fails in ~20s with the four jobs behind it skipped -- verified
on the rc3 run, where plan is `failure` and build-local-artifacts,
build-global-artifacts, host and announce are all `skipped` -- against ~3
minutes for a real build. The workflow ALSO triggers on pull_request, where it
plans without a tag and passes in ~20s, so a green 20s run on a PR and a failed
20s run on a tag look alike at a glance and mean opposite things.

Every claim above was read off `gh`, not inferred. Step 5 of RELEASING.md said
"if plan succeeds and the four jobs skip" in a first draft; the run says plan
FAILS, and it was corrected rather than kept as a plausible-sounding shape.

No version bumped, no tag cut, no tag deleted, the rc1/rc2 releases untouched.
Whether the stale rc tags are ever removed is left as the user's call and
deliberately not made a step: it is not part of cutting a release, and the
principle that covers it -- a published tag is not moved -- is stated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-21 22:28:15 -07:00

2.9 KiB

Releasing

A checklist, not an explanation. The reasoning is context/code/tooling.md §4; read it if a step surprises you, not before.

Its own file rather than a README.md section because README.md §Installation addresses someone installing improvise and this addresses the one person publishing it — folding them together lengthens the user-facing document for no user benefit.

The one rule

Bump the version and commit it BEFORE you tag. dist matches the tag against the root package's version and refuses when they disagree — and by the time CI tells you, the tag is public on three remotes and a published tag is not moved. Every step below exists to put that check before the irreversible one.

Cutting a release

  1. Bump the version. Cargo.toml's [package] version is the one dist announces (dist-workspace.toml says members = ["cargo:."]). Keep the crates under crates/ in step with it; gp-tree sits at its own version deliberately, and the preflight lists such crates as a note rather than an error.

  2. Commit the bump, and let Cargo.lock be part of that commit.

  3. Run the preflight, with the tag you intend to push:

    scripts/release-preflight.sh vX.Y.Z
    

    It refuses a dirty tree, refuses a tag that does not match the version, runs dist plan as a second opinion when dist is on PATH, and prints the tag commands only once it has passed. It cuts nothing itself.

  4. Tag and push, using the commands it printed:

    git tag -a vX.Y.Z -m "vX.Y.Z"
    git push gh vX.Y.Z
    

    Push the tag to gh — .github/workflows/release.yml triggers on a tag push there, matching **[0-9]+.[0-9]+.[0-9]+*. origin and gitea are mirrors and build nothing.

  5. Watch the run. A green release takes about three minutes and publishes tarballs for all four targets with their .sha256 files, sha256.sum, dist-manifest.json, source.tar.gz and improvise-installer.sh.

    The failure signature is unmistakable once you know it: plan fails in about twenty seconds and the four jobs behind it skip. That is the tag not matching the version — see below. A run that gets past plan is building.

When a tag has already been pushed against the wrong version

Leave it dead and cut the next one. Do not move or re-push the tag: all three remotes carry it, and a published tag that changes meaning is worse than one that never built. Bump to the next version, commit, run the preflight, and tag that.

v0.1.0-rc3 is the standing example — pushed while every crate still said 0.1.0-rc2, so dist refused and the four jobs behind plan skipped. It has no GitHub release and is left as it is.

A tag with no release is not a tag to clean up on the way to cutting one; whether the stale rc tags are ever removed is a separate decision, and not a step here.