Skip to content

CI's Python matrix runs one interpreter three times, plus two workflow deprecations #69

Description

@fsecada01

Chasing two warnings in the 0.3.1 release run turned up something bigger, so this ticket covers all of it. Everything here lives in .github/workflows/, so it is one file surface and one branch.

1. The Python matrix does not test more than one Python

ci.yml declares matrix: python-version: ["3.11", "3.12", "3.13"] and passes it to astral-sh/setup-uv@v3:

- uses: astral-sh/setup-uv@v3
  with:
    python-version: ${{ matrix.python-version }}

setup-uv@v3 has no python-version input. It warns and ignores it:

##[warning]Unexpected input(s) 'python-version', valid inputs are ['version', 'checksum', 'github-token', ...]

The next step then runs bare uv venv, which resolves whatever interpreter the runner happens to ship. From the three matrix jobs of run 30725981899:

Job name Interpreter actually used
test (3.11) CPython 3.12.3 at /usr/bin/python3
test (3.12) CPython 3.12.3 at /usr/bin/python3
test (3.13) CPython 3.12.3 at /usr/bin/python3

All three run the same interpreter. The matrix value only ever reaches the job name, which is what makes this worth fixing rather than tidying: the PR checks page reads as three-version coverage, and a reviewer has no signal that it is one version run three times. Every green check on every PR to date has been that.

pyproject.toml declares requires-python = ">=3.11" and classifiers through 3.14, so the package claims support for four versions and has been verified on one. 3.11 is the floor and therefore the likeliest to break — nothing in the suite has ever exercised it.

Fix: either bump to astral-sh/setup-uv@v5 (which does take python-version) or keep v3 and pass the version explicitly at venv creation:

- run: uv venv --python ${{ matrix.python-version }}

The second is the smaller change and does not depend on the action's input surface at all.

Whatever the mechanism, the workflow should assert the interpreter rather than assume it, so this cannot silently regress again:

- name: Confirm the interpreter matches the matrix
  run: |
    got=$(python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
    [ "$got" = "${{ matrix.python-version }}" ] || {
      echo "matrix says ${{ matrix.python-version }}, interpreter is $got"; exit 1; }

Without that step, the fix is unverifiable from the checks page — the same property that let this sit unnoticed.

release.yml:18-20 has the identical mistake with a hardcoded python-version: "3.12". It is harmless there by luck: the runner default currently is 3.12, so the wheel is built with the intended interpreter. It is a pure-Python wheel (py3-none-any), so nothing shipped is wrong. Fix it anyway — the reason it is correct today is coincidence, not configuration.

2. actions/download-artifact@v4 is on deprecated Node 20

From the same run:

Node.js 20 is deprecated. The following actions target Node.js 20 but are being
forced to run on Node.js 24: actions/download-artifact@v4

release.yml:117. Bump to @v5. upload-artifact@v4 at release.yml:99 should be checked in the same pass. Cosmetic today, breaking whenever GitHub finishes the removal.

3. Decision, not a defect: release.yml is not gated on ci.yml

release.yml triggers on push: tags: ["v*"] and runs its own build. It does not require the tagged commit's CI to have passed, so a tag on a red master publishes to PyPI regardless.

This is not hypothetical: 0.3.0 was published from a commit whose master CI was red. Harmlessly — the red was missing checks rather than failing tests — but the guard that would have caught a genuine failure was not there.

Worth deciding rather than assuming. Options, in ascending order of cost:

  • Leave it, and treat tagging as the human gate it already is in practice.
  • Add a job to release.yml that queries the tagged SHA's check runs and fails if any concluded failure.
  • Move to branch protection with required checks, so a red commit cannot reach master to be tagged.

The third is the real fix and the only one that also closes #62's original concern, which was closed as wontfix precisely because branch protection was not in place.

Checklist

  • The matrix runs three distinct interpreters, verified from the logs
  • A step asserts the interpreter matches the matrix value, and fails when it does not
  • release.yml pins its build interpreter by configuration rather than by runner default
  • download-artifact (and upload-artifact, if affected) off deprecated Node 20
  • Item 3 resolved as a decision and recorded, whichever way it goes
  • Whatever 3.11 turns up once it is genuinely exercised is fixed or filed

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions