Repository navigation
ci(release): publish GitHub Releases with gh and the release App - #167
Merged
Merged
Conversation
The tag run for v1.3.0 published to PyPI but failed to create the
GitHub Release: the semantic-release publish action refuses the
detached tag checkout ("Detached HEAD state cannot match any release
groups"). Replace it with `gh release create "$tag" dist/* --verify-tag`
using the release App token, with that version's CHANGELOG section as
the notes. Before creating the Release, prove dist/* is exactly the
PyPI artifacts by SHA-256, after moving out the *.publish.attestation
files the PyPI action writes into dist/.
A tag run uses release.yml from the tagged commit, so re-running it
cannot pick up this fix. Add a publish_existing_tag dispatch from
protected main that publishes only the GitHub Release for an existing
tag. It verifies current main, an annotated tag made by the release
App, containment in main, the tagged version and CHANGELOG, exact-tag
test and hosted qualification evidence, and a rebuild of the tagged
source. It downloads the PyPI artifacts, checks them against PyPI's
digests, requires them to equal the rebuild, and uploads those bytes.
It never publishes to PyPI and never creates or moves a tag.
Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
verify_release_publication.py writes no step outputs when PyPI and the GitHub Release already match, so the not-on-PyPI guard must trigger on an explicit publish_pypi=true only. fetch-pypi already fails when the version is absent from PyPI. Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
Member
Author
|
Self-review before merge (head 02e847d):
Signed-off-by: Richard Abrich richard.abrich@mldsai.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.
Why
The v1.3.0 tag run (39081035898) published the wheel and sdist to PyPI. It then failed at "Publish the GitHub Release through the release App":
python-semantic-release/publish-actionrefuses the detached tag checkout ("Detached HEAD state cannot match any release groups"). This was the first real run of the tag-publication path from #92.That tag run can't be repaired by re-running it, because a tag run uses
release.ymlfrom the tagged commit. A fresh dispatch refuses too, because the tag already exists. And nothing may be created by hand.What changes
Tag path (
publish-tag)gh release create "$tag" dist/* --verify-tag --title "$tag" --notes-file …, withGH_TOKENset to the release App token.## vX.Y.Z (date)section of CHANGELOG.md, written byscripts/release_assets.py notes.*.publish.attestationfiles thatpypa/gh-action-pypi-publishwrites intodist/. PyPI serves those itself, and the existing verifier would reject them as extra assets.release_assets.py check-pypithen proves thatdist/*is exactly one wheel and one sdist, and that PyPI serves those exact SHA-256 digests. The existing final check (verify_release_publication.py --wait-seconds 180) still requires both surfaces.New:
publish_existing_tagdispatch from protected main (publish-existing-tag-release)For a tag that is already on PyPI but has no GitHub Release:
vVERSIONtag must exist, be annotated, haveopenadapt-release[bot]as its tagger (the same identity the tag event requires as actor), and be contained in main.check_changelog.pymust pass, and the CHANGELOG heading must be present.check_release_ci.py --sha <tagged sha>requires exact-SHA test and hosted qualification. Nothing is dispatched.verify_distribution.pyandcheck_source_boundary.py --require-dist.release_assets.py fetch-pypidownloads the PyPI files (fromfiles.pythonhosted.orgonly) and checks each against PyPI's SHA-256. The set must equal the rebuild byte for byte, and those PyPI bytes becomedist/.fetch-pypi, with an explicit guard after it), because this path never publishes to PyPI. If an exact Release already exists, it creates nothing and only runs the final check, so a rerun after success passes. Otherwise it runsgh release create "$tag" dist/* --verify-tagwith the App token, followed by the same final check of both surfaces.git tag, and nogit push. The job's own token iscontents: read; only the App token can write.create-release-tagis skipped whenpublish_existing_tagis set. Its "tag already exists" message now points at the new path.Verification
uv buildof 66c9d25 reproduces both PyPI 1.3.0 files exactly (dde6c7d4…wheel,ebe5751b…sdist), so the rebuild equality holds for v1.3.0.tests/test_release_assets.py(14 tests) covers:pytest tests: 1051 passed, 22 skipped. actionlint is clean, ruff is clean, andcheck_changelog.pyis OK.After merge I'll dispatch
release.ymlfrom main withversion=1.3.0andpublish_existing_tag=true.Signed-off-by: Richard Abrich richard.abrich@mldsai.com