Every release uses ./scripts/release.sh. Do not bump versions, tag, or create GitHub Releases manually.
- Install GitHub CLI (
gh) and authenticate. - Ensure PyPI trusted publishing is configured for this repo (
publish.ymluses thepypiGitHub environment).
Releases are cut in CI by the Cut Release workflow (cut-release.yml). It runs
the same ./scripts/release.sh a person would, as the Hotdata automation app.
-
Make sure
## [Unreleased]inCHANGELOG.mdhas user-facing notes. -
Trigger the workflow with the bump kind or an explicit version:
gh workflow run cut-release.yml -f bump=minor # or patch | major | 1.2.3The
preparejob runsrelease.sh prepare, which opens therelease/vX.Y.ZPR with auto-merge enabled. -
The PR merges on its own once CI and the review gate pass. Merging it runs the
publishjob, which runsrelease.sh publishand pushes thevX.Y.Ztag.
That is the whole process. Running the script locally still works and does the same thing, but is not the normal path.
Pushing the vX.Y.Z tag triggers two workflows:
| Workflow | Purpose |
|---|---|
publish.yml |
Build wheel/sdist and publish to PyPI |
release.yml |
Create the GitHub Release with notes from CHANGELOG.md |
If the publish workflow failed before PyPI accepted the upload — a stale action pin, a PyPI outage — re-run it against the same tag rather than deleting the tag or burning a version number:
gh workflow run "Publish to PyPI" --ref main -f tag=vX.Y.Z--ref main selects the workflow definition, so a fix landed since the tag is
picked up; the tag input selects what gets built and published.
Only safe while the version is unpublished. PyPI refuses to replace an existing file, so a retry after a successful upload fails at the upload step rather than overwriting. Check first:
curl -s -o /dev/null -w '%{http_code}' https://pypi.org/pypi/hotdata/X.Y.Z/json404 means the version is still free.
If PyPI publish succeeded but the GitHub Release workflow failed, rerun it from main
without retagging:
gh workflow run "GitHub Release" --ref main -f tag=vX.Y.ZThe tag must already exist on the remote. The workflow checks out that tag, extracts the
matching CHANGELOG.md section, and creates or updates the GitHub Release.
- PR check (
check-release.yml): ifpyproject.tomlversion changes,CHANGELOG.mdmust contain a matching## [X.Y.Z]section. - Tag check (
publish.yml): the tag (withoutv) must match[project].versioninpyproject.toml. - Publish guard (
release.sh publish): refuses to tag if the changelog section is missing.
Together, these make it hard to ship a version without changelog notes or a GitHub Release.