Repository navigation
Automate releases: Prepare release and release-on-merge workflows - #6
Conversation
….toml hatchling read the version from __init__.py, which `uv version` can't read or bump. A static [project] version is what uv manages, so libraries keep version = "0.1.0" in pyproject.toml, build with uv_build (which takes src/<package>/ with no further config), and read __version__ back from the installed package metadata. scriv reads pyproject.toml for every kind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A pull request or tag created with the default Actions token starts no workflows, so the release workflows start ci.yml and publish.yml explicitly. A dispatched publish still runs as publish.yml, which is what PyPI trusted publishing checks; it can't run as a reusable workflow. publish.yml now refuses any ref but a v* tag and checks the tag against the built wheel's version. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Prepare release (run from the Actions tab) bumps the version with `uv version --bump`, collects changelog.d/ with scriv, opens a "Release vX.Y.Z" pull request, and starts CI on it. Its auto bump picks minor for Added/Changed/Deprecated/Removed and patch for Fixed/Security, never major. Merging the pull request runs release-on-merge, which checks the version, tags the merge commit, creates the GitHub release from the changelog section, and starts publish.yml for PyPI projects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RELEASING.md and the scaffold README describe the Prepare release flow, its one-time Actions setting, and the manual fallback. The template CHANGELOG records the additions and the upgrade steps. Template CI parses the new workflows in both variants, checks the PyPI step appears only for libraries, and builds the library with uv_build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of 600567cI reviewed this in a fresh clone. I rendered three variants with Two findings break the documented flow, and I would fix both before merging. The rest are smaller. 1. The dispatched CI run never satisfies the required check (high)Where: Problem [docs]: GitHub's troubleshooting required status checks page says only checks from The premise behind the dispatch is also out of date. Since the 2026-06-11 changelog, a pull request that Failure scenario [reasoned from the docs]: take a repo with the template's Fix: drop 2.
|
…pdate uv_build ships only the license files [project] license-files names, so MIT projects name LICENSE; hatchling included it by default. Updating a library across 1.10.0 merges the template's version = "0.1.0" into pyproject.toml with no conflict, silently resetting a released project. A copier migration now sets it back to the __version__ the project's __init__.py had at HEAD, which copier leaves untouched since it only updates a clean tree. Tested by updating a v1.9.1 library released at 0.4.2: pyproject.toml ends at 0.4.2. From the review on #6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…unnable Checks from a dispatched run don't satisfy a required check, but a pull request opened with the default token does start its pull_request runs, in an approval-required state. So Prepare release no longer dispatches ci.yml or needs actions: write, and ci.yml drops its dispatch trigger; the PR body says to approve the runs. Prepare release now fails clearly when run from another branch, on a fragment with entries but no heading, on an existing tag, or with a release PR already open, and force-pushes its own branch so a re-run after a partial failure works. Release on merge checks the base against the default branch, creates the tag at the merge commit itself (refusing one already on another commit), and skips an existing tag or release on re-run. Release notes lose their leading blank line, and publish.yml takes the current checkout and setup-uv pins. From the review on #6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RELEASING.md, the scaffold README and the template CHANGELOG say to click Approve workflows to run on the release PR, describe the re-run and tag safeguards, and replace the upgrade note's wrong conflict list with what the migration does. Template CI drops the ci.yml dispatch check and checks that LICENSE reaches the library's wheel. From the review on #6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, this caught a real design flaw. Actioned in c00283c, cfe5dac and 737cdd4:
All variants (app, PyPI library, library without PyPI) render valid YAML, and zizmor finds nothing in any of them. The full template CI step and the rendered hooks pass locally. Posted by Claude Code on Matt's behalf. |
Why
Releasing a scaffolded project by hand takes about six steps: bump the version, collect the changelog, open a PR, merge it, tag it, and create the GitHub release. This PR automates them. You run one workflow from the Actions tab, review the pull request it opens, and merge it.
What changes
Libraries: uv_build and a static version (f15897a, c00283c)
[project] versioninpyproject.toml, which is whatuv version --bumpmanages.__version__reads it back from package metadata.__init__.py, which uv can't bump. So libraries build with uv's own backend,uv_build, which takessrc/<package>/with no further config.license-files = ["LICENSE"], because uv_build ships only the licence files named there.version = "0.1.0"in silently. A copier migration sets the version back to the__version__the project had.Publish dispatch and guard (7d1d060, cfe5dac)
publish.ymlgainsworkflow_dispatch, and release-on-merge starts it explicitly.publish.yml. That matters for PyPI trusted publishing, which can't run from a reusable workflow. Existing publishers need no change.publish.ymlnow refuses any ref but av*tag, and fails if the tag doesn't match the built wheel's version.ci.ymltoo. A review showed that doesn't work: dispatched checks don't satisfy a required check. cfe5dac removes it. A PR opened with the default token does start its ownpull_requestruns, held at Approve workflows to run, and those count.Prepare release and release-on-merge (aab78c5)
prepare-release.yml(manual, with a bump choice). It picks the bump:autogives minor for Added, Changed, Deprecated or Removed, and patch for Fixed or Security only, and never gives major. It then runsuv version --bump,scriv collectand mdformat, pushesrelease/vX.Y.Z, opens a "Release vX.Y.Z" pull request whose body is the changelog section, and says in the PR body to approve its CI runs. It refuses to run from another branch, with no fragments, with a fragment that has entries but no heading, with an existing tag, or with a release PR already open. A re-run after a partial failure works.release-on-merge.yml. When arelease/v*pull request from this repository merges into the default branch, it checks thatpyproject.tomlat the merge commit agrees with the branch, creates the tag at the merge commit (refusing one already on another commit), and creates the GitHub release. A re-run skips steps that already succeeded. For PyPI projects it then startspublish.yml. Apps get no PyPI step and noactions: writepermission.Docs and checks (600567c)
One-time setting per repo, and one click per release
Turn on Settings → Actions → General → Allow GitHub Actions to create and approve pull requests. Then, for each release PR, click Approve workflows to run to start its CI. It's off in Generality-Labs/inspect-evals-lint and Generality-Labs/inspect_dataset today. The weekly template-update workflow needs it too: it has only succeeded so far because there was nothing to update.
Testing
Fixedalone picks patch, and adding anAddedfragment picks minor.__version__is 0.1.0,uv buildworks, and afteruv version --bump patchthe nextuv runsees 0.1.1.uv version --short --frozen, which release-on-merge uses, leavesuv.lockuntouched.__init__.py, and updated it to this branch tagged locally as v1.10.0. The migration ran,pyproject.tomlends atversion = "0.4.2"on uv_build, and only__init__.pyconflicts.dist-info/licenses/LICENSE, and template CI now checks for it.🤖 Generated with Claude Code