Skip to content

fix: tombstone releases so sub-3.12 pip install fails loudly - #240

Merged
Jammy2211 merged 3 commits into
mainfrom
feature/sub-312-install-tombstone
Aug 19, 2026
Merged

fix: tombstone releases so sub-3.12 pip install fails loudly#240
Jammy2211 merged 3 commits into
mainfrom
feature/sub-312-install-tombstone

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

Closes part 1 of #238.

What this fixes

pip install autolens on Python 3.9, 3.10 or 3.11 does not fail — it silently
backtracks to 2026.7.29.1 and installs the whole stack: no JAX, no warning,
exit 0, three weeks stale and predating the JAX-default install (PyAutoLens#702).
The install docs claim it "will fail with a no matching distribution error". It
does not.

Reproduced on real interpreters (3.9/3.10/3.11 venvs, pip 26.2.1), not simulated:

Python pip install autolens
3.9 silently installs the 2026.7.29.1 stack, exit 0
3.10 same
3.11 same
3.12 2026.8.17.1 (current)

The cause is not a defect in our metadata. 2026.7.29.2 was the first release
published with Requires-Python >=3.12; everything at or below 2026.7.29.1
was published with >=3.9, and PyPI metadata is immutable, so those releases
remain valid pip candidates forever. Raising a floor never retracts the back
catalogue.

What this adds

  • autohands/tombstone.py — builds one sdist-only release per package at
    2026.7.29.1.post1, declaring Requires-Python <3.12, whose build raises
    with an explanation naming the user's actual Python version. It outranks every
    sub-floor candidate and is invisible at or above the floor.
  • .github/workflows/publish_tombstone.ymlworkflow_dispatch-only
    publish. The PyPI tokens live in Actions secrets and should stay there; the
    real index additionally requires typing publish tombstones as confirmation.
  • tests/test_tombstone.py — 15 tests.

Deliberately not wired into release.yml. The tombstones are one-off: every
future release declares >=3.12, so it is invisible below the floor and the
tombstone stays the top sub-floor candidate indefinitely. Republishing a
deliberately-broken artifact on each release would be noise.

Verification

Not just unit-tested — the built artifacts were resolved against the real
PyPI candidate set
on real interpreters:

scenario result
py3.9 / 3.10 / 3.11 pip install autolens loud failure, each naming the correct running version
py3.12 pip install autolens 2026.8.17.1 — unaffected, tombstone never seen
py3.10 autolens==2026.7.29.1 still resolves — pinned historical installs keep working
py3.10 --only-binary=:all: falls back to the old wheel silently — the one hole

That last row is a real limit, documented rather than hidden. Closing it would
mean retracting the back catalogue, and yanking ~330 releases per package is
both semantically wrong and impossible in bulk (PyPI exposes no yank API).

Full suite: 329 passed.

API Changes

None. No PyAuto* library API is touched — this adds a build tool and a manual
workflow to PyAutoHands, and no existing entry point changes behaviour.

There is an outward-facing change once the workflow is run, which is a
separate human act: pip install of the five packages below Python 3.12 starts
failing with an explanation instead of installing 2026.7.29.1. Resolution at
3.12+ is byte-for-byte unchanged, and exact pins of historical releases continue
to resolve on old Pythons.

No pending-release label: this does not depend on a release, and gating it on
one would block the fix. Most PyAutoHands PRs carry no label.

Follow-ups (tracked in #238)

  1. Merge this — workflow_dispatch only registers on the default branch.
  2. Run publish_tombstone against testpypi, verify, then pypi with the
    confirm phrase.
  3. Phase 2: correct the false notes in docs/installation/pip.md for PyAutoLens,
    PyAutoGalaxy and PyAutoFit (they also name the wrong cut release — the real
    one is 2026.7.29.2, not 2026.4.5.3), and extend PyAutoHeart
    verify_install Check B to assert the unpinned sub-3.12 install fails.

Heart at ship time: verdict: stale, score 85, zero red and zero yellow
reasons. The single stale reason — "release validation stale: source moved
since rehearsal (PyAutoNerves, PyAutoFit, PyAutoArray, PyAutoGalaxy,
PyAutoLens)"
— concerns the five library repos and is unrelated to this change.

🤖 Generated with Claude Code

Jammy2211 and others added 2 commits August 19, 2026 18:23
Raising `requires-python` does not retract anything. 2026.7.29.2 was the first
release published with `Requires-Python >=3.12`; everything at or below
2026.7.29.1 was published with `>=3.9`, and PyPI metadata is immutable, so those
releases stay valid pip candidates forever. On 3.9/3.10/3.11 `pip install
autolens` therefore does not fail — it backtracks to 2026.7.29.1 and installs
the whole stack silently, no JAX and no warning. The install docs claim a "no
matching distribution" error that does not happen.

Adds `autohands/tombstone.py`: builds one sdist-only release per package at
2026.7.29.1.post1 with `Requires-Python <3.12`, whose build raises with an
explanation naming the user's Python version. It outranks every sub-floor
candidate and is invisible at or above the floor, so pip below 3.12 reports why
instead of quietly installing stale code.

Verified against the real PyPI candidate set with the real artifacts:

  py3.9/3.10/3.11  pip install autolens  -> loud failure, correct version named
  py3.12           pip install autolens  -> 2026.8.17.1, unaffected
  py3.10           autolens==2026.7.29.1 -> still resolves (pins keep working)

The one hole — `--only-binary=:all:` skips sdists and still lands on the old
wheel — is documented rather than hidden; no packaging mechanism closes it.

One-off publish, deliberately not wired into release.yml: future releases all
declare >=3.12, so the tombstone stays the top sub-floor candidate forever.

Issue: #238

Co-Authored-By: Claude Opus 5 <[email protected]>
The tombstones cannot be published from a laptop — the PyPI tokens live in
Actions secrets, and they should stay there. This adds a workflow_dispatch-only
job that builds via autohands/tombstone.py and uploads with the same twine
version, retry policy and --skip-existing behaviour as release.yml.

Deliberately not wired into release.yml: the tombstones are one-off, and
republishing a deliberately-broken artifact on every release would be noise.

Publishing to the real index requires typing `publish tombstones` as the
confirm input. A released filename is permanent — TestPyPI is the rehearsal.

Issue: #238

Co-Authored-By: Claude Opus 5 <[email protected]>
The tenant firewall flagged `url="https://github.com/PyAutoLabs"` as an instance
fact hardcoded in organ code. Removing the field is the right fix rather than
allowlisting the file: a tombstone's PyPI page has no use for a homepage link,
and its README already carries the whole explanation.

Co-Authored-By: Claude Opus 5 <[email protected]>
@Jammy2211

Copy link
Copy Markdown
Collaborator Author

CI green on 193814e — Hands Tests 3.12 / 3.13 / 3.14 all pass.

The earlier red on f171f5a was the tenant firewall, not the tests: the generated setup() carried url="https://github.com/PyAutoLabs", an instance fact hardcoded in organ code. Fixed by dropping the field rather than allowlisting the file — a tombstone's PyPI page has no use for a homepage link, and its README already carries the explanation.

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