Files
python-sdk/RELEASE.md
zeekay 0f5676c200 one release path, and the docs say the one that runs
RELEASE.md described GitHub Actions, GitHub secrets and three bin/ scripts
that each took a PyPI token off the environment. None of that publishes this
repo — .hanzo/workflows/publish-pypi.yml does, reading the token from KMS —
so the scripts are gone and the page describes the lane, its tag mapping, and
what to check before a tag makes a version permanent.

LLM.md carries the published state: hanzoai 3.2.13 with get_keys/get_tools,
and the KMS route that a 404 will otherwise misreport as a missing secret.
2026-08-14 17:51:02 -07:00

2.0 KiB

Releasing

One lane: .hanzo/workflows/publish-pypi.yml, on the git.hanzo.ai runner fleet. It reads the PyPI token from KMS at publish time — nothing else in this repo uploads, and no token is ever handed to a shell here.

Bump the version, commit, push a tag:

# pkg/hanzo-mcp/pyproject.toml -> version = "0.15.15"
git commit -am "hanzo-mcp 0.15.15"
git push origin main
git tag hanzo-mcp-0.15.15 && git push origin hanzo-mcp-0.15.15

The tag names the packages:

tag publishes
v3.2.13 every workspace package
hanzoai-v3.2.13 the root hanzoai client alone
hanzo-tools-0.3.5 every pkg/hanzo-tools-*
hanzo-mcp-0.15.15 the matching pkg/<name>, else hanzo

A v* tag is not as broad as it looks: uploads pass --skip-existing, so a package whose version is already on PyPI is a no-op. What it does publish is every package whose in-tree version is new — which is the point, and also the reason to look before you tag.

Watch the run under Actions on git.hanzo.ai. Versions are semver, and PyPI never lets a version be replaced: a bad release is fixed by a higher one.

Before you tag

Build what the lane will build and read the metadata you are about to publish — the registry page is the first thing a reader sees, and it is permanent.

python -m build . --outdir dist          # or pkg/<name>
python -m twine check dist/*

twine check catches a README that will not render. It does not catch a missing one: a package with no readme, license or authors in its pyproject.toml uploads happily and lands as a blank page.

If the lane fails

Read the job log — every failure mode it knows about prints a line saying which credential or path it wanted. The KMS read is the one worth naming: KMS serves GET /v1/kms/secrets/<path>/<name>?env=<env> and takes the org from the caller's token, so a request in any other shape 404s exactly like an unseeded path. Prove a route against a secret you know exists before concluding a secret is missing.