Repository navigation
Add the tag-triggered release workflow (PyPI Trusted Publishing + GitHub Release) - #6
Merged
Merged
Conversation
A pushed vX.Y.Z tag validates the tag against pyproject.toml and CHANGELOG, runs lint, types and tests, builds the wheel and sdist once, and checks them. It then publishes those exact files to PyPI through Trusted Publishing (OIDC, environment pypi) and creates the GitHub Release with the same files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
.github/workflows/release.yml. Pushing avX.Y.Ztag becomes the release: the distributions are validated and built once, those exact files go to PyPI through Trusted Publishing, and then a GitHub Release is created with the same files. Nothing is tagged or published by this PR.Flow
push: tags: ["v*.*.*"]only. There is nopull_request, branch push orworkflow_dispatchtrigger. The publish and release jobs also checkgithub.ref_type == 'tag'.contents: read):^vX.Y.Z$,X.Y.Zequals thepyproject.tomlversion,CHANGELOG.mdhas a## [X.Y.Z] -section,HEAD == GITHUB_SHA, and the tag resolves toGITHUB_SHA.GITHUB_SHAand that no GitHub Release exists yet.uv sync --locked,ruff check,ruff format --check,mypyandpyteston Python 3.11.uv build --no-sourceswithSOURCE_DATE_EPOCHset to the commit time.dist/must hold exactlydecguard-X.Y.Z-py3-none-any.whlanddecguard-X.Y.Z.tar.gz.twine check --strict, confirms the sdist builds a wheel, and records SHA-256 digests as a job output.decguard --version(must equaldecguard X.Y.Z),validateandteston the refund example.distartifact.id-token: writeonly, environmentpypi): downloads the artifact, checks the digests against the build job's output, then runspypa/gh-action-pypi-publishwith no credentials, so Trusted Publishing is used. Attestations are on by default.contents: writeonly,needs: [build, publish]): downloads and checks the same files, re-checks the remote tag, and fails if a release for the tag already exists. It then runsgh release create "$GITHUB_REF_NAME" … --verify-tag --generate-notes, which never creates a tag.Security:
permissions: {}.persist-credentials: false.OPENROUTER_API_KEYand no PyPI token. The paid real-backend tests are not run.One deviation from "GH_TOKEN only in the release job": the build job's pre-publish check uses the read-only
github.token, in that single step. It refuses a tag that already has a GitHub Release, for example one created from the web UI, before anything reaches PyPI. Otherwise PyPI would get the files and the release job would fail afterwards.Verification
actionlintwith shellcheck: clean.zizmor --persona=pedantic(online): no findings.The tag check was run locally against a real git repository:
v0.1.0passes, and an annotatedv0.1.0passes too.v0.2.0: version mismatch;v0.1,v0.1.00andv0.1.0-rc1: notvX.Y.Z;GITHUB_SHA;Dry run on Actions (run 36074530320) using a throwaway branch and a copy of this workflow. The copy had a branch trigger instead of tags, no
pypienvironment, noid-token, no publish action, andechoinstead ofgh release create. All three jobs passed:twine checkPASSED for both files, sdist → wheel OK, clean-wheel smoke OK;sha256sum --checkgaveOKfor the wheel and sdist.The throwaway branch was deleted afterwards. There are no tags or releases, PyPI returns 404 for
decguard, and no environment was created.Local:
ruff check,ruff format --check,mypy,pytest(313 passed, 8 skipped) andsyngraphe checkall pass.Setup before pushing
v0.1.0decguard, ownersuffro, repositorydecguard, workflowrelease.ymland environmentpypi.pypienvironment is created automatically on the first run. You can restrict its deployments tov*.*.*tags, so a modifiedrelease.ymlon a branch cannot publish, and add required reviewers.The release procedure is in
CONTRIBUTING.md#releasing.🤖 Generated with Claude Code