Skip to content

👷 ci(release): split into release and tag-triggered publish#3042

Merged
gaborbernat merged 2 commits intopypa:mainfrom
gaborbernat:main
Feb 16, 2026
Merged

👷 ci(release): split into release and tag-triggered publish#3042
gaborbernat merged 2 commits intopypa:mainfrom
gaborbernat:main

Conversation

@gaborbernat
Copy link
Contributor

@gaborbernat gaborbernat commented Feb 16, 2026

The release workflow was broken in several ways after the recent restructure: running tox r -e release locally pushed a commit and tag but nothing in CI reacted to the tag push, so no PyPI publish, no GitHub Release, and no get-virtualenv update happened. The workflow_dispatch path worked end-to-end but the release commit failed the check workflow because towncrier-generated changelog.rst was committed without running docstrfmt. The towncrier config also had name = "tox" instead of "virtualenv".

This splits the monolithic release.yaml into two workflows that converge on the same publish path regardless of how the release is triggered. 🔀 release.yaml (workflow_dispatch) now only handles Phase 1: generating the changelog, running pre-commit to ensure formatting is correct, committing, tagging, and pushing. A new publish.yaml triggers on any tag push matching *.*.* and handles Phase 2: building sdist/wheel/zipapp, publishing to PyPI via trusted publisher (OIDC), creating a GitHub Release with the zipapp attached, and updating get-virtualenv. If the publish fails, a rollback job automatically reverts everything.

The key insight is that both local (tox r -e release) and CI (workflow_dispatch) releases now produce the same artifact — a tagged commit on main — and the tag push is what triggers publishing. 🔐 PyPI publishing always happens via the trusted publisher in the release environment, never locally. The release.yaml uses GH_RELEASE_TOKEN for checkout so the push triggers publish.yaml (pushes with GITHUB_TOKEN don't trigger other workflows).

@gaborbernat gaborbernat changed the title main 👷 ci(release): split into release and tag-triggered publish Feb 16, 2026
@gaborbernat gaborbernat force-pushed the main branch 2 times, most recently from 4439552 to 50a52b1 Compare February 16, 2026 14:52
The release workflow was broken in multiple ways: local releases didn't
trigger any CI publishing, towncrier output wasn't formatted before
committing (failing pre-commit in check workflow), and the GitHub
release was never created.

Split into two workflows that converge on the same path regardless of
how the release is triggered (locally or via workflow_dispatch):

- release.yaml (workflow_dispatch): generates changelog, runs
  pre-commit, commits, tags, and pushes
- publish.yaml (tag push): builds sdist/wheel/zipapp, publishes to
  PyPI via trusted publisher, creates GitHub Release with zipapp,
  updates get-virtualenv, with automatic rollback on failure

Also fixes towncrier config naming tox instead of virtualenv.

Signed-off-by: Bernát Gábor <bgabor8@bloomberg.net>
Symlinks with .cmd extension get routed through cmd.exe on Windows,
which cannot execute Python scripts. Only .exe and bare symlinks are
valid for Python interpreter discovery.
@gaborbernat gaborbernat merged commit 1ecf0ed into pypa:main Feb 16, 2026
53 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments