Skip to content

Publishing on every merged PR is hard to control #95

Description

@Marius1311

Publishing on every merged PR is hard to control

publish.yaml currently triggers on a merged pull request:

on:
  pull_request:
    types: [closed]
    branches: [main]
jobs:
  publish:
    if: github.event.pull_request.merged == true

and then bumps the patch version, commits it to main, tags, builds and publishes
to PyPI. So every merge is a release. I ran into several consequences of that
while working on #91/#92/#93, so collecting them here.

Consequences

  1. There is no such thing as an unreleased merge. A docs typo, a CI tweak, a
    comment change or an internal refactor all ship a new version to PyPI. Two PRs
    merged back to back are two releases; changes cannot be batched into one.
  2. The version number carries no information. The bump is always patch, so a
    new feature or a breaking change cannot be released as minor or major without
    intervening by hand. 0.4.6 means "six merges", not anything semantic.
  3. Publishing is not gated on tests. The publish job has no needs: and does
    not run pytest. test.yaml runs on the pull request, but nothing re-checks the
    merge result, and nothing stops a publish when the suite is red — which it
    currently is on main (see Drop tensorflow_probability so scenvi imports on current jax #93).
  4. It cannot work for pull requests from forks. Fork-triggered pull_request
    workflows get no secrets and a read-only GITHUB_TOKEN, so both
    secrets.PYPI_TOKEN and git push origin HEAD:main should fail. Import ENVI lazily so a broken jax stack cannot take COVET down #91 and COVET: make preprocessing explicit and batch_size numerically inert #92 are
    the first fork PRs on this repo — every previously merged PR came from a
    dpeerlab: branch — so this has not been exercised yet. Worth confirming before
    merging them.
  5. CI pushes directly to main. The version bump is committed and pushed with
    contents: write, which bypasses any branch protection you might otherwise want.
  6. Concurrent merges race. Two PRs merged close together both read
    poetry version -s from the same starting point and bump to the same number; the
    second push or publish then fails.
  7. A long-lived API token. PYPI_TOKEN is a stored credential with upload
    rights, rather than PyPI's OIDC trusted publishing.

Suggestion

Trigger on a published GitHub release instead:

on:
  release:
    types: [published]

and publish with pypa/gh-action-pypi-publish
under permissions: {id-token: write}, using PyPI trusted publishing — no stored
token. Take the version from the tag rather than committing a bump, which also
removes the CI push to main entirely.

This is what scverse's
cookiecutter template does, and
adopting its conventions here (hatchling, PEP 621 metadata, release-triggered
trusted publishing) would be a reasonable base more broadly — it would also make
the two-package version story in #94 considerably easier.

Cutting a release then becomes: merge what you want, when you want; press
"Draft a new release" when the set of changes is worth publishing; write release
notes; done.

Happy to open a PR for this if you'd like — it is a small change, but it is your
release process, so I did not want to send one unasked.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions