Release process
The authoritative reference for cutting a new release of whestbench to PyPI, covering the steady-state flow, one-time setup, and troubleshooting notes.
This document is the authoritative reference for cutting a new release
of whestbench to PyPI. It covers the steady-state flow, the one-time
setup that must happen outside the repo, and a few troubleshooting
notes.
TL;DR (steady-state)
git checkout main && git pull origin main
uv run cz bump --dry-run # preview the next version + CHANGELOG entry
uv run cz bump # writes pyproject version + CHANGELOG.md + creates v<x.y.z> tag
git push --follow-tags # tag push triggers the publish workflow
# … open GitHub Actions → approve the `publish-pypi` job → wait ~30s →
# package on PyPI + GitHub Release createdPre-release tags: uv run cz bump --prerelease alpha produces tags
like v0.5.0a0.
What happens after git push --follow-tags
The tag push fires
.github/workflows/pypi-publish.yml,
which:
- Builds the sdist + wheel with
uv build. - Pauses for approval in the
pypiGitHub environment (manual gate). - Publishes to PyPI via Trusted Publishing (OIDC; no API token stored in repo secrets).
- Creates a GitHub Release whose body is the matching CHANGELOG section for the tag.
End result: uv add whestbench / pip install whestbench works ~2
minutes after a maintainer clicks "approve" on the publish-pypi job.
One-time setup (per maintainer, per repo)
Before the first release will succeed, two things must be configured outside the repo.
1. PyPI Trusted Publisher
On pypi.org, as an account with Owner or
Maintainer rights on the whestbench project (or as the user
creating it, if not yet published):
- "Your projects" →
whestbench→ "Publishing" → "Add a pending publisher" (or "Add a publisher" if the project already exists). - Fill in:
- PyPI project name:
whestbench - Owner:
AIcrowd - Repository name:
whestbench-public - Workflow filename:
pypi-publish.yml - Environment name:
pypi
- PyPI project name:
PyPI's "pending publisher" feature allows trusted publishing to succeed on the very first publish of a brand-new project name.
2. GitHub pypi environment
In the whestbench-public repo on GitHub:
- Settings → Environments → "New environment" → name:
pypi. - Enable "Required reviewers".
- Add yourself (and any other release maintainers) as reviewers.
- Save.
Without this, publishes proceed without a human approval gate. The Trusted Publishing OIDC handshake will still work — there is just no gate to abort a bad tag.
How CHANGELOG entries get into the GitHub Release
The publish workflow extracts the body of the matching ## v<version>
section in CHANGELOG.md using an awk script and uses it as the
GitHub Release notes. Commitizen writes section headers in the
## v<version> (<date>) form, which the workflow expects.
When promoting an existing ## Unreleased section to a versioned
release manually (rather than via cz bump), use the same header
format: ## v0.4.0 (2026-05-26).
If no matching section is found, the workflow falls back to a default
body: Release v<x.y.z>\n\nSee CHANGELOG.md for details.
Troubleshooting
Publish job fails with "Trusted publisher not configured"
PyPI side is not configured. Re-check step 1 of "One-time setup". The
workflow filename and environment name must match exactly
(pypi-publish.yml, pypi).
Publish job fails with "File already exists on PyPI"
A version was previously uploaded and yanked. PyPI does not allow re-uploading the same version, even after a yank. Resolution: delete the tag locally and on the remote, bump to the next version, retag:
git tag -d v0.5.0
git push origin :refs/tags/v0.5.0
uv run cz bump # bumps to v0.5.1
git push --follow-tagsGitHub Release step fails after PyPI succeeded
The package is on PyPI; only the GitHub Release is missing. Re-run
the workflow on the same tag from the GitHub Actions UI. The
github-release job's gh release create is the only remaining side
effect and is idempotent against the existing tag (will fail if a
release already exists, succeed if not).
cz bump --dry-run previews an unexpected version
The previewed version is computed from conventional-commits types in
the commit range since the last tag. feat → minor bump (under v1.x
behaviour: still minor while major_version_zero = true in
[tool.commitizen]), fix → patch, feat! or BREAKING CHANGE →
minor while major_version_zero = true, else major. To bump to a
specific version explicitly, use cz bump --increment PATCH|MINOR|MAJOR.
Pin updates for flopscope
Whestbench pins flopscope>=0.4.1 and flopscope-server>=0.4.1.
When flopscope ships a new minor or major version, bump these floors
in pyproject.toml and re-run uv lock before cutting the next
whestbench release. (Out of scope for an automated workflow; flag if
Dependabot becomes worth the noise.)