Skip to content

Publish to PyPI with trusted publishing and create the GitHub release - #41

Merged
hardbyte merged 2 commits into
mainfrom
claude/review-issues-prs-deps-xnrrmr
Aug 19, 2026
Merged

Publish to PyPI with trusted publishing and create the GitHub release#41
hardbyte merged 2 commits into
mainfrom
claude/review-issues-prs-deps-xnrrmr

Conversation

@hardbyte

Copy link
Copy Markdown
Owner

Two changes to the release job in ci.yml, plus the docs that describe it.

Trusted publishing

PYO3/maturin-action with MATURIN_PYPI_TOKENpypa/gh-action-pypi-publish, which exchanges the job's short-lived GitHub OIDC token for a PyPI upload credential. The job already declared id-token: write without using it; now that permission does something and no long-lived PyPI token is stored in repository secrets.

  • The job runs in a pypi environment (https://pypi.org/p/common-expression-language), which is what the trusted publisher is scoped against and gives you somewhere to add an approval gate later.
  • Artifacts are collected into dist/ via pattern: wheels-* + merge-multiple: true, since the action publishes dist/ by default. skip-existing: true preserves the old --skip-existing behaviour on re-runs.

GitHub release creation

Pushing v0.8.0 published to PyPI but left no GitHub release, because a tag and a release are separate objects and nothing in the repo created the latter. The job now does, from the tag:

  • Release notes come from the matching ## [X.Y.Z] section of CHANGELOG.md, extracted with awk. Verified against the real file: 46 lines for 0.8.0, empty for a version with no section — in which case it warns and falls back to --generate-notes rather than failing a release that has already published to PyPI.
  • Wheels and sdist are attached, and --verify-tag refuses to invent a tag that doesn't exist.
  • If the release already exists (re-run of a tag), it refreshes the assets and exits instead of erroring.

This needs contents: write on the job, alongside id-token: write.

Docs

docs/contributing.md's release process said to bump the version in pyproject.toml — the version actually lives in Cargo.toml, with pyproject.toml deriving it through maturin. Corrected, and the tag-driven flow, the trusted-publishing setup, and the workflow/environment names the publisher is tied to are now written down.

Required one-time setup before the next tag

This PR cannot configure PyPI, so the next release will fail to upload unless this is done first:

  1. PyPI → common-expression-language → Manage → Publishing → Add a new pending/trusted publisher (GitHub):
    • Owner hardbyte, repository python-common-expression-language
    • Workflow name ci.yml
    • Environment pypi
  2. Repo → Settings → Environments → create pypi (optionally restrict it to tag pushes).
  3. Once a release has published successfully, delete the now-unused PYPI_API_TOKEN secret.

All three values must match the workflow exactly — renaming ci.yml or the environment means updating the publisher on PyPI too.

Verification

The workflow parses (yaml.safe_load) with the expected permissions, environment and step order; the awk extraction was exercised against CHANGELOG.md for both the present and missing-section cases; docs tests, ruff format --check and ruff check pass. The release path itself can only be exercised by a real tag push, so 0.9.0 will be its first live run — the skip-existing, --verify-tag and already-exists guards are there so a partial failure is recoverable.

Note that v0.8.0 itself still has no GitHub release; this automation only applies from the next tag. Happy to write the notes for a backfilled v0.8.0 release if you create it (the tag push I attempted was blocked — this session's credentials can push branches but not tags).


Generated by Claude Code

Swaps the stored PYPI_API_TOKEN for PyPI trusted publishing: the release job
runs in a `pypi` environment and uses pypa/gh-action-pypi-publish, which
exchanges the job's short-lived GitHub OIDC token for an upload credential.
The job already requested `id-token: write` without using it; now it does.
Artifacts are collected into dist/ with a wheels-* pattern so the action finds
them where it expects.

The job also creates the GitHub release for the tag. Pushing v0.8.0 published to
PyPI but left no release behind, because a tag and a GitHub release are separate
objects and nothing created the latter. It now extracts the matching CHANGELOG
section as the release notes (falling back to generated notes if the section is
missing rather than failing the release), attaches the wheels and sdist, and
re-uploads assets instead of erroring if the release already exists.

Documents the flow in docs/contributing.md, which also had the version bump in
the wrong file — the version lives in Cargo.toml, and pyproject.toml derives it
through maturin.

Requires one-time setup on PyPI: register this repository, the ci.yml workflow
and the pypi environment as a trusted publisher, then delete the PYPI_API_TOKEN
secret.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a3903543a6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/ci.yml
`gh release create` with assets creates a draft, uploads, then publishes, and
deletes the draft if an upload fails. If the job dies before that cleanup runs —
cancelled run, runner loss — a draft survives, and the already-exists branch
would then upload assets and exit successfully, leaving the release invisible.
Check isDraft on that path and publish it.
@hardbyte
hardbyte merged commit 6594a38 into main Aug 19, 2026
20 checks passed
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.

2 participants