Pipeline #40358 (v0.1.4) failed with the same OIDC error as 0.1.0:
TrustedPublishingFailure: Unable to retrieve an OIDC token from
the CI platform for trusted publishing GitLab: Environment
variable PYPI_ID_TOKEN not found
Twine 6's default is --trusted-publishing automatic, which ALWAYS
tries OIDC first when it detects GitLab CI env vars, regardless of
whether TWINE_USERNAME/TWINE_PASSWORD are set. Adding
'--trusted-publishing never' forces classic API token auth via env
vars (or .pypirc) and skips the OIDC dance entirely.
Plus a sanity-check that the group-scoped TWINE_USERNAME and
TWINE_PASSWORD vars actually landed in the env on this pipeline
('set' or 'MISSING' — never echoes the value). If they're MISSING
despite being configured at the python/ group level, the most likely
cause is the vars being marked Protected while the tag isn't a
protected ref (Settings -> Repository -> Protected tags).
Pending publisher registered on PyPI for python/erldistpy with
workflow filepath .gitlab-ci.yml — GitLab now mints a short-lived
OIDC ID token (audience=pypi) per pipeline, twine 6+ exchanges it
for a scoped upload token. No long-lived secret on the runner.
What this commit changes:
- .gitlab-ci.yml pypi-twine stage gains id_tokens: PYPI_ID_TOKEN
- pip install twine (no <6 pin) — twine 6 is OIDC-native
- pyproject.toml build-system requires drops the setuptools<77 cap
(latest setuptools emits Metadata-Version 2.4 which twine 6 reads
fine; locally verified)
After this tag publishes successfully, the same pattern goes to:
- ago (python/ago)
- make-post-sell (engineering/make-post-sell)
- remarkbox (engineering/remarkbox)
each gets a Trusted Publisher entry added on the PyPI project page,
then the id_tokens block lands in their CI.
Recipe lives at docs/PYPI-TRUSTED-PUBLISHING.md (updated to reflect
the new auth model is now live for erldistpy).
Pipeline #40349 (v0.1.2) failed because the test stage ran on the tag
push and failed (env mismatch on the runner), which skipped the
pypi-twine stage downstream. MPS uses 'except: - tags' on test so
only pypi-twine runs for tag pipelines; mirror that.
Test stage still runs on every branch push, just not on tag pushes.
Pipeline #40344 (v0.1.1) failed at twine check with 'Metadata is
missing required fields: Name, Version' — twine 5.x doesn't recognize
Metadata-Version 2.4 wheels (it caps at 2.3). setuptools 77.0 (March
2025) bumped wheels to 2.4 for PEP 639 license-expression support.
Capping the build env's setuptools at <77 keeps wheels on Metadata-
Version 2.2 (verified locally — twine 5 PASSED).
Long-term fix is Trusted Publishing OIDC, which lets us drop the
twine<6 pin AND the setuptools cap. See the migration plan in our
next commit / pipeline discussion.
twine 6 (Sep 2025) auto-detects GitLab CI envvars and refuses to
fall back to ~/.pypirc on the runner, requiring PYPI_ID_TOKEN
(Trusted Publishing OIDC) instead. Pipeline #40338 (v0.1.0 tag) hit
this and failed with TrustedPublishingFailure.
Pinning twine<6 restores classic ~/.pypirc auth that ago /
make_post_sell / remarkbox already use on the same runner. When we
migrate to Trusted Publishing as a coordinated change across all four
python/* repos, we'll drop this pin and add an id_tokens block + PyPI
trusted-publisher config.
7 phases shipped: ETF codec, EPMD client, v6 handshake (MD5 cookie),
data channel (4-byte framing), gen_call protocol, TLS dist
(inet_tls_dist), unfeed integration. Maps + cross-stack validation
through wallet.unsandbox.com's test pyramid.
122 tests across unit / handshake / channel / node / tls / etf-maps.
Tag this commit with vX.Y.Z and push the tag — CI's pypi-twine stage
takes over from there.
pyproject.toml gains the metadata PyPI expects:
- Real author name + email
- Keywords (erlang, elixir, distribution, gen_server, ...)
- Classifiers (status, license, Python versions, topic)
- Project URLs (Homepage / Repository / Bug Tracker)
- readme content-type = text/markdown so PyPI renders our README
- build + twine added to the [dev] extra
.gitlab-ci.yml mirrors ago's two-stage pattern:
- test stage on every push (venv + pip install -e .[dev] + pytest)
- pypi-twine stage only on tags: python -m build, twine check,
twine upload. Credentials come from GitLab CI variables
TWINE_USERNAME (typically __token__) and TWINE_PASSWORD.
Makefile gains build / dist-check / publish-test / publish targets so
the release flow is also runnable locally if a dry run is needed.
Release flow:
1. Bump version in pyproject.toml
2. Commit + push
3. git tag -a vX.Y.Z -m 'release vX.Y.Z' && git push --tags
4. CI's pypi-twine stage picks up the tag and uploads
README.md adds an Install section + Quick start (plain dist + TLS dist)
so PyPI's project page shows usable copy on first visit. dist/ artifacts
build cleanly and both pass twine check.