Skip to content

fix(ci): resolve a tomllib-capable interpreter, unpin twine, allow release retries - #44

Merged
anoop-narang merged 2 commits into
mainfrom
fix/release-tooling
Aug 11, 2026
Merged

fix(ci): resolve a tomllib-capable interpreter, unpin twine, allow release retries#44
anoop-narang merged 2 commits into
mainfrom
fix/release-tooling

Conversation

@anoop-narang

Copy link
Copy Markdown
Contributor

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.

Why now

0.5.0 needs releasing so hotdata-dlt-destination can adopt hotdata 0.9 — it inherits this package's cap through its [ibis] extra. With the tooling as it stands, prepare fails immediately and publish would fail after the tag is public.

Precedent

Same fixes as sdk-python-framework#58/#63 and sdk-python#159/#162, adapted to this repo — its release.sh differs slightly in ordering and uv handling, so the interpreter logic was applied in place rather than copied wholesale.

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
anoop-narang requested a review from a team as a code owner August 11, 2026 17:10
@anoop-narang
anoop-narang requested review from shefeek-jinnah and removed request for a team August 11, 2026 17:10
Comment thread .github/workflows/release.yml
claude[bot]
claude Bot previously approved these changes Aug 11, 2026
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.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
anoop-narang merged commit a07a48e into main Aug 11, 2026
7 checks passed
@anoop-narang anoop-narang changed the title fix(ci): make releases possible and retryable here too fix(ci): resolve a tomllib-capable interpreter, unpin twine, allow release retries Aug 11, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant