Publishing a release
The Treeship release-and-publish workflow. A reproducible procedure so a maintainer, or the next session picking up release work, can cut a release with confidence.
Publishing a release
This page documents the release-publish workflow. It exists so the maintainer can run a release with confidence and so the next session (human or agent) picking up release work has a reproducible procedure.
Treeship ships from a single repo to three package registries (crates.io, npm, PyPI) plus a hosted Hub and a docs site. Tagging is the irreversible action that triggers the publish pipeline, so the release script splits prepare (safe, reversible) from tag (gated, audited).
The two-phase model
The release script (scripts/release.sh) is deliberately split so accidental tagging is impossible:
scripts/release.sh prepare <version>bumps every version site, runs preflight, and commits. It never tags. Safe to run inside a normal feature workflow.scripts/release.sh tag <version> --sha <sha> [--yes]creates the annotated tag. It requires an explicit subcommand, an explicit target SHA (never implicitHEAD), a clean working tree, no pre-existing local or remote tag, and either--yesor interactive confirmation. It never pushes; you rungit push origin v<version>after reviewing.
This split exists because tagging triggers the entire publish pipeline. It must require an explicit, audited gesture.
Triggering a release
-
Bump and verify every version site. Run the preparer, which bumps the Rust crates, the npm packages (
packages/sdk-ts,bridges/mcp,bridges/a2a,packages/verify-js), and the npm wrapper (treeshipplus the@treeship/*platform packages), then commits:scripts/release.sh prepare 0.12.0The preparer runs the version preflight for you. You can also run it directly to confirm every manifest, version file, and internal dependency pin agrees:
scripts/check-release-versions.py 0.12.0It exits
0when every site matches and1(with a table of disagreements) when any disagree. -
Update the changelog. Add a section for the new version with
Added / Changed / Deprecated / Removed / Fixedsubsections, following the existing entries inCHANGELOG.md. The Changelog docs page mirrors this file. -
Open the bump + changelog PR and merge to
main. Land the version bump as a normal reviewed PR. Do not tag the feature branch. -
Tag from the merged
mainSHA. After CI is green onmain, create the annotated tag against the explicit merged SHA:git checkout main && git pull scripts/release.sh tag 0.12.0 --sha <merged-main-sha> --yesThe tag string uses the human-facing form (
v0.12.0); the version strings in the manifests use the same numeric form (0.12.0). -
Push the tag to start the publish pipeline. The script does not push for you. Review the tag, then:
git push origin v0.12.0The tag push triggers
.github/workflows/release.yml, which builds and publishes the crates, npm packages, and PyPI distribution.
After the publish
-
Verify the CLI installs from a clean machine across every supported target. The platform smoke (
scripts/smoke-platform-release.sh) confirms, per distro, thatnpm install -g treeship@<version>exits0,treeship --versionprints the expected version,treeship initsucceeds in a tmpdir, andtreeship attest actionsigns and returns an artifact id.python -m venv /tmp/verify-publish source /tmp/verify-publish/bin/activate pip install treeship-sdk==0.12.0 deactivate && rm -rf /tmp/verify-publish -
Redeploy the Hub / API so the hosted device-flow and
/v1/dock/*endpoints match the released CLI. -
Deploy the docs.
docs.treeship.devrebuilds automatically on push tomainvia Vercel's Git integration; no separate publish step is needed there. -
Run a fresh customer-path smoke: activate a dock, attest a receipt, push to the Hub, and verify, so the released CLI, Hub, and docs agree end to end.
If npm install or pip install fails, the usual causes are: the registry has not indexed the new version yet (transient, usually under a minute), the wheel or package build omitted a required file (check the package manifests), or a version-string mismatch between the tag and the published package.
Rolling back
Package registries do not allow re-uploading the same version once published. If a release ships broken, do not delete or yank silently and re-tag the same number. Cut a new patch version (0.12.1) with the fix, run the full prepare → tag → push flow again, and note the superseded release in the changelog. The original tag and its receipts stay in history; the fix moves forward.
Why a gated tag
The version bump is reversible: it is a commit on a branch, reviewed like any other change. The tag is not. A pushed tag triggers irreversible publishes to crates.io, npm, and PyPI, none of which allow re-uploading a version. The prepare / tag split, the required explicit --sha, the clean-tree guard, and the no-auto-push rule all exist so that the irreversible step is a deliberate, audited gesture, not a side effect of running a script.