Tag releases again - #326
Conversation
publish-git-tag.sh called 'python .github/fetch_version.py'. That file has never existed in this repository, so the command failed, git tag received an empty argument, and '|| true' discarded the error. Releases have gone to PyPI untagged since v0.4.4 while the version reached 1.5.2. Reads the version from pyproject.toml with the same regex bump_version.py uses, and drops the '|| true' so a failure surfaces. The script now exits cleanly when the tag already exists locally or on the remote, so reruns and backfills are safe. The publish job also had no permissions block, so it relied on whatever the repository default happens to be; contents: write is now explicit.
juaristi22
left a comment
There was a problem hiding this comment.
Confirmed the diagnosis and checked the CI preconditions the new script depends on, not just the script.
Diagnosis. The last real publish job (105179078388, the 1.5.2 release) logged python: can't open file '.../.github/fetch_version.py' followed by Everything up-to-date, so the tag step has been a silent no-op. The accidental v1.5.2 tag points at de08443, which is the tip of main and the commit that set the version to 1.5.2, so it is correct. I would keep it.
Script. Ran it against a throwaway bare remote from a full clone and from a depth-1 clone. It pushes when the tag is absent, exits 0 when the tag exists locally or only on the remote, works with only python3 on PATH and with no git identity configured, and exits 1 when pyproject.toml has no version line. The regex matches the one in bump_version.py.
CI preconditions.
- The publish job's checkout is
actions/checkout@v6with no token override. v6 still defaultspersist-credentialsto true, so the default token is in the git config and the push authenticates.fetch-depth: 0also fetches tags, so the local check sees existing ones. - Job-level
contents: writeis what the push needs and overrides the org default. - The only ruleset on the repo is
main(branch target, deletion and non-fast-forward). No tag rulesets and no legacy tag protection. - The bump commit is pushed with the App token, so it re-triggers the workflow and the publish job checks out that exact commit. The tag lands on the "Update package version" commit, as
v1.5.2does now. skip-existing: trueon the PyPI step plus the script's early exits make a rerun after a partial failure safe in either direction.
One suggestion, not blocking: run the tag step after the PyPI upload. As ordered now, a tag failure blocks the release. Everything above checks out, but the tag push is the one step not yet proven in CI, and moving it after pypa/gh-action-pypi-publish costs nothing: with skip-existing: true a rerun is still safe, and a surprise can never hold back a release.
Merge order. Suggest merging this first and alone, so the first real run tags 1.5.3 with nothing user-facing in it, and waiting for both workflow runs to finish before merging #324 or #325. Two Versioning runs in flight at once race on the same version number.
Per review: as ordered, a tag failure blocked the release. The tag push is the one step not yet exercised in CI, and skip-existing on the upload keeps a rerun safe either way.
|
Taken — the tag step now runs after Thank you for checking the CI preconditions rather than just the script; Keeping On merge order: agreed, and I will not merge this myself — @vahid-ahmadi is reviewing. Noting here for whoever does that this should go in first and alone, and both Versioning runs should finish before #324 or #325 follow, so the two do not race on the same version number. |
Per review: the release step sat after the two early exits, so it ran only when the script also pushed a new tag. Whenever the tag already existed the script exited 0 before reaching it - which meant a rerun after a failed release create went green having done nothing, the same silent failure #326 removed for tags, reintroduced for releases. The tag section is now an if/else that skips tagging rather than exiting, so the release check is always reached. Simulated against a bare remote with a stub gh: a fresh tag pushes then creates; an existing tag with no release still creates; both present exits 0 after one release view; a failing create turns the job red. Adds --verify-tag, so gh aborts if the tag is absent from the remote rather than creating one itself at the default branch head. Release notes now come from the towncrier CHANGELOG section for the version, falling back to --generate-notes when that section is absent.
* Create the GitHub release automatically, not just the tag Zenodo archives on the GitHub release, not on the tag, so a tag alone leaves the DOI pointing at whatever was last released by hand. v1.5.5 was created manually for the JOSS submission; without this, the next archived version would be whenever someone remembered to click the button, while PyPI moved ahead. The tag step now creates the release too, skipping if one already exists, so reruns and the existing v1.5.5 are safe. The job already declares contents: write, which is what gh release create needs; it just needs GH_TOKEN in the environment. * Reach the release check on every run, not only on a fresh tag Per review: the release step sat after the two early exits, so it ran only when the script also pushed a new tag. Whenever the tag already existed the script exited 0 before reaching it - which meant a rerun after a failed release create went green having done nothing, the same silent failure #326 removed for tags, reintroduced for releases. The tag section is now an if/else that skips tagging rather than exiting, so the release check is always reached. Simulated against a bare remote with a stub gh: a fresh tag pushes then creates; an existing tag with no release still creates; both present exits 0 after one release view; a failing create turns the job red. Adds --verify-tag, so gh aborts if the tag is absent from the remote rather than creating one itself at the default branch head. Release notes now come from the towncrier CHANGELOG section for the version, falling back to --generate-notes when that section is absent. * Decide tagging on the remote, not the local tag store gh release create --verify-tag reads the tag from the remote, so a tag that exists only locally must still be pushed; the old local-first branch skipped the push and made the release step abort.
Releases have been going to PyPI untagged since v0.4.4, while the version reached 1.5.2 — roughly thirty releases with no tag behind them. No tags means no GitHub releases, which means Zenodo has nothing to archive and cannot mint the DOI the JOSS submission needs.
Cause
.github/publish-git-tag.shwas:.github/fetch_version.pyhas never existed in this repository.git log --all -- .github/fetch_version.pyreturns nothing. So the command substitution failed,git tagreceived an empty argument and errored, and|| truediscarded it. The step has been reporting success while doing nothing for about two years.The change
pyproject.tomlusing the same regex.github/bump_version.pyalready uses, rather than a helper that does not exist.|| truefrom the workflow step, so a failure to tag fails the job instead of passing silently.command -v python || command -v python3.permissions: contents: writeto the publish job, which had nopermissionsblock at all and so depended on whatever the repository default happens to be. Pushing a tag needs it.Disclosure: I pushed the v1.5.2 tag by accident
While testing the script's idempotency I ran it from a scratch copy of this repository that still had
originpointing at PolicyEngine/microdf. The second run took the "tag does not exist" branch and did what it is designed to do — it pushed. That was an unintended write to a shared repository, and the test should have pointed at a throwaway remote.The tag it created is correct:
v1.5.2points atde08443, "Update package version", which is the commit that set the version to 1.5.2 and is the current tip ofmain, matching what is on PyPI. So the repository now has its first correct release tag in thirty releases, by accident rather than by design.If you would rather the workflow created it,
git push --delete origin v1.5.2removes it and the next release will tag normally. Either way the code change stands on its own.Verification
Both early-exit paths tested:
and the push path is what produced the tag described above, so it is confirmed working rather than merely reviewed.
After this merges