fix(ci): resolve a tomllib-capable interpreter, unpin twine, allow release retries - #44
Merged
Conversation
Found while trying to cut 0.5.0: this repo has the same three release-tooling defects already fixed in sdk-python-framework (#58, #63) and sdk-python (#159, #162). All three would have hit in sequence. release.sh hardcoded `python3` in every helper while using tomllib, which is stdlib only from 3.11 -- so `prepare` fails outright wherever `python3` is older, with a bare ModuleNotFoundError that reads as a broken checkout. Now resolves an interpreter that can actually import it, falling back to `uv` (which `prepare` already shells out to for `uv lock`), and resolves lazily so `--help` still works without one. gh-action-pypi-publish was pinned at v1.13.0, whose bundled twine rejects `Metadata-Version: 2.5` that current hatchling emits. This is what left hotdata-framework v0.11.0 tagged and released with nothing on PyPI. Note the build job's own `twine check --strict` passes, because it installs a current twine -- the failure only appears at upload, after the tag is public. Neither workflow had a workflow_dispatch, so a publish that failed for reasons unrelated to the code left only two options: delete and re-push the tag, or burn a version number. Both now take a tag input, using a RELEASE_TAG env threaded through the concurrency group, checkout ref, version check and release name. Also: make_latest was unconditional `true`, which would move the "Latest" badge onto an older tag when a dispatch repairs one -- `legacy` lets GitHub decide by tag date. And dependabot watched only pip, which is how the publish action pin went stale unnoticed; github-actions added. No change to what a release does. Verified: `--help` works with PATH stripped, prepare/publish refuse with a message naming the interpreter, and with uv present the script reads hotdata-ibis 0.4.0 and computes 0.5.0. 109 tests pass.
anoop-narang
requested review from
shefeek-jinnah
and removed request for
a team
August 11, 2026 17:10
I added the pre-checkout guard to publish.yml and not to release.yml, which is the asymmetry the reviewer caught -- and release.yml is the one that needs it more. It holds contents: write, and action-gh-release CREATES a tag when tag_name does not resolve to one, so an unvalidated `tag: main` would check out cleanly and then leave refs/tags/main plus a release named "hotdata-ibis main", both needing manual cleanup. The push path is constrained by the v[0-9]* filter; the dispatch path had no constraint at all. Same guard, same strict form, so the input contract now matches in both. Noting for elsewhere: sdk-python-framework has the same gap, which I introduced when adding its dispatch. sdk-python was already safe -- its dispatch predates this work and came with validation.
There was a problem hiding this comment.
Prior nit addressed: release.yml now validates the dispatch tag before checkout, in the same strict ^v[0-9]+\.[0-9]+\.[0-9]+$ form as publish.yml, so the contents: write path can no longer be handed an arbitrary ref that action-gh-release would materialize as a new tag. No new findings.
anoop-narang
added a commit
to hotdata-dev/sdk-python-framework
that referenced
this pull request
Aug 11, 2026
* fix(ci): validate the dispatch tag in release.yml Gap I introduced in #63, caught in review of the same port to hotdata-ibis (hotdata-dev/hotdata-ibis#44). publish.yml got a pre-checkout guard on the dispatch input; release.yml did not, despite needing it more. release.yml holds contents: write, and action-gh-release CREATES a tag when tag_name does not resolve to one. So an unvalidated `tag: main` checks out cleanly and then leaves refs/tags/main plus a release named for it, both needing manual cleanup. publish.yml at worst wastes a run. The push path is constrained by the v[0-9]* tag filter; the dispatch path had no constraint at all. Same guard and same strict form as publish.yml, so the input contract matches in both. sdk-python already had this -- its dispatch predates this work. * fix(ci): give publish.yml the same strict dispatch guard My description claimed parity between the two workflows and it was not true. In this repo publish.yml never gained a pre-checkout step -- #63 only switched its existing "Verify tag matches pyproject version" check to $TAG, which runs AFTER checkout and is looser (^v[0-9]). So `-f tag=v1.2` or `v1.2.3rc1` was accepted there and rejected in release.yml. Tightening publish.yml rather than loosening release.yml, since release.sh only ever produces X.Y.Z -- it enforces ^[0-9]+\.[0-9]+\.[0-9]+$ on explicit versions, so the strict form is the correct contract. Both workflows now validate the same input the same way, before fetching an arbitrary ref. The post-checkout version match stays: it catches a tag that is well-formed but does not match pyproject.
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.
Found while trying to cut 0.5.0: this repo has the same three release-tooling
defects already fixed in sdk-python-framework (#58, #63) and sdk-python (#159,
#162). All three would have hit in sequence.
release.sh hardcoded
python3in every helper while using tomllib, which isstdlib only from 3.11 -- so
preparefails outright whereverpython3is older,with a bare ModuleNotFoundError that reads as a broken checkout. Now resolves an
interpreter that can actually import it, falling back to
uv(whichpreparealready shells out to for
uv lock), and resolves lazily so--helpstill workswithout one.
gh-action-pypi-publish was pinned at v1.13.0, whose bundled twine rejects
Metadata-Version: 2.5that current hatchling emits. This is what lefthotdata-framework v0.11.0 tagged and released with nothing on PyPI. Note the
build job's own
twine check --strictpasses, because it installs a currenttwine -- the failure only appears at upload, after the tag is public.
Neither workflow had a workflow_dispatch, so a publish that failed for reasons
unrelated to the code left only two options: delete and re-push the tag, or burn
a version number. Both now take a tag input, using a RELEASE_TAG env threaded
through the concurrency group, checkout ref, version check and release name.
Also: make_latest was unconditional
true, which would move the "Latest" badgeonto an older tag when a dispatch repairs one --
legacylets GitHub decide bytag date. And dependabot watched only pip, which is how the publish action pin
went stale unnoticed; github-actions added.
No change to what a release does. Verified:
--helpworks with PATH stripped,prepare/publish refuse with a message naming the interpreter, and with uv present
the script reads hotdata-ibis 0.4.0 and computes 0.5.0. 109 tests pass.
Why now
0.5.0 needs releasing so
hotdata-dlt-destinationcan adopthotdata0.9 — it inherits this package's cap through its[ibis]extra. With the tooling as it stands,preparefails immediately andpublishwould fail after the tag is public.Precedent
Same fixes as
sdk-python-framework#58/#63andsdk-python#159/#162, adapted to this repo — itsrelease.shdiffers slightly in ordering and uv handling, so the interpreter logic was applied in place rather than copied wholesale.