Skip to content

Fix interpreter-bound wheels treated as portable (#1048) - #1053

Merged
Mikola Lysenko (mikolalysenko) merged 4 commits into
mainfrom
agent/fix-pypi-interpreter-bound-wheel
Oct 8, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 4 commits into
mainfrom
agent/fix-pypi-interpreter-bound-wheel

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #1048

Summary

Hosted scan no longer pins a patched wheel that only one Python interpreter can install (e.g. six-1.16.0-cp311-none-any.whl) into Pipfile.lock or any other PyPI lock. Such a wheel is now withheld with redirect_pypi_platform_wheel, like ABI- and platform-tagged wheels already were. Vendored mode now gives it the vendor_platform_locked advisory.

Root cause

vendor::pypi_distribution::tag_is_platform_specific decided portability from the ABI and platform tags only ([_py, abi, plat] => abi != "none" || plat != "any") and never looked at the python tag. So a wheel bound to one interpreter version (cp311-none-any, pp310-none-any), or one that excludes Python 3 (py2-none-any), counted as portable. Both the hosted refusal (pypi_platform_wheel_refusal) and vendored mode use this rule, so hosted mode narrowed a cross-version lock to that wheel and reported success. pipenv sync then failed on every other CPython minor.

Fix

A wheel is portable only when its ABI is none, its platform is any, and its (possibly compressed, .-separated) python tag set contains a generic Python 3 tag: py3 or py3<minor>. pip accepts those on any later 3.x. As the issue's table notes, py311-none-any stays portable. The warning text now says "interpreter- or platform-specific", and CLI_CONTRACT.md documents the rule.

tag before after
py3-none-any, py2.py3-none-any, py311-none-any, cp311.py3-none-any portable portable
cp311-none-any, pp310-none-any, cp311.cp312-none-any portable withheld
py2-none-any portable withheld
*-abi3-*, *-manylinux*, *-win_amd64 withheld withheld

Tests (red → green)

All of these failed on main and pass with the fix:

  • patch::redirect::platform_wheel_tests::*: every hosted lane (uv.lock CRLF/LF, PEP 723 script lock, pylock.toml, Pipfile.lock across Pipenv majors, requirements.txt, poetry.lock, pdm.lock, Hatch) now also asserts a cp311-none-any wheel is withheld, warned about once with the tag named, and not confirmed. The tag table test adds py3, py311, cp311.py3 (portable) and cp311, pp310, py2, cp311.cp312 (withheld).
  • vendor::pypi::tests::platform_specific_tag_detection and wheel_platform_filename_fallback_is_fail_closed: unit coverage for the tag rule.
  • vendor::pypi::tests::interpreter_bound_tag_sets_platform_locked_and_warns: vendored build from a Tag: cp311-none-any dist records platform_locked and warns.
  • CLI in_process_redirect_pipenv::interpreter_bound_wheel_is_not_pinned_into_the_lock: the end-to-end hosted Pipenv scan from the issue leaves Pipfile.lock and requirements.txt untouched, exits 0, and a same-run VEX attests nothing. I confirmed it fails with the old pypi_distribution.rs and passes with the fix.

Per-issue checklist:

Commands run locally:

  • cargo clippy --workspace --all-features -- -D warnings: clean.
  • cargo test -p socket-patch-core --all-features: 5586 passed, 4 failed. The 4 are chmod-based write-failure tests that can't fail as intended as root in this sandbox (wire_failure_rolls_back_already_written_files, wire_write_failure_maps_error_and_leaves_lock_untouched, an_unremovable_hidden_lock_keeps_every_store_entry, relax_loop_must_not_traverse_symlinked_root), unrelated to this change.
  • cargo test -p socket-patch-cli --all-features for in_process_redirect_pipenv, in_process_vendor, mode_migration_pypi, e2e_vex_vendor, in_process_redirect_pdm: all pass. covgap_commands_vendor 51/54; the 3 failures are the same root-only *_state_write_failure_* class.
  • cargo fmt --all -- --check is not clean on main itself (CI doesn't run it), so I formatted only the lines this PR changes.
  • The npm/pypi/gem wrappers are untouched; they don't classify wheel tags.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WjTGVb5owoBCsFnVKYUJLP


Note

Medium Risk
Changes shared wheel-tag logic for all hosted PyPI redirects and vendored PyPI flows; behavior shifts for previously “portable” cpXY-none-any wheels but is intentionally fail-closed to avoid broken cross-interpreter lockfiles.

Overview
Fixes #1048: wheels that only one Python interpreter can install (e.g. cp311-none-any) are no longer treated as portable when classifying patch artifacts.

tag_is_platform_specific in pypi_distribution.rs now requires ABI none, platform any, and a generic Python 3 python tag (py3 / py3<minor> in the compressed tag set). Interpreter-bound tags (cp311, pp310), Python-2-only tags, and existing ABI/platform locks still trigger refusal. Hosted PyPI redirect continues to skip pinning via redirect_pypi_platform_wheel; vendored mode surfaces vendor_platform_locked with updated “interpreter- or platform-specific” copy. CLI_CONTRACT.md documents the broader rule.

Tests cover all hosted PyPI lanes (including Pipenv e2e), vendored platform_locked, and expanded tag tables.

Reviewed by Cursor Bugbot for commit 6bff12d. Configure here.

Assisted-by: Claude Code:claude-opus-5-5
A patched wheel tagged cp311-none-any installs only on CPython 3.11,
but the portability check looked at the ABI and platform tags and
ignored the python tag. Hosted scan pinned such a wheel into
Pipfile.lock (and every other PyPI lock) with no warning, so installs
on any other Python version failed. Vendored mode gave no
vendor_platform_locked advisory for it either.

A wheel now counts as portable only when its python tag set holds a
generic Python 3 tag (py3 or py3<minor>). cpXY / ppXY and py2-only
wheels get redirect_pypi_platform_wheel in hosted mode and the
vendor_platform_locked advisory in vendored mode, like ABI- and
platform-tagged wheels already did.

Fixes #1048

Assisted-by: Claude Code:claude-opus-5-5
Adds a CLI-level regression test for #1048: a hosted scan of a
Pipenv project granted a cp311-none-any wheel leaves Pipfile.lock
and requirements.txt untouched, exits 0 and attests nothing.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 7, 2026 16:50
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor 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.

Stale Bugbot comment from a previous run.

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Status on 0c7a92c:

  • Bugbot: no issues found.
  • All four CodeQL Analyze jobs (actions, javascript-typescript, python, rust; run 37653071626) failed without running any step. runner_id is 0 and no runner was assigned during the 22 minutes they sat in queue. That's runner starvation during today's backlog, not this diff. The same dynamic CodeQL workflow passed on the recent agent PRs. GitHub refuses a re-run of this dynamic run (403 This workflow run cannot be retried), so it will run again on the next push. If CodeQL is required to merge, a maintainer can re-run it from the Security tab.
  • Every other completed check is green. The macOS legs are still queued.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run

@cursor cursor 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 6bff12d. Configure here.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 7, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Ready for review at 6bff12d.

  • Changes: merged current origin/main into the branch with no conflicts, which brings in the new ci.yml and the merge-queue ci-ok job. No code changes were needed. Locally, cargo clippy --workspace --all-features -- -D warnings passes on Linux CI. On macOS it fails on a lint that comes from main (unix_default unused in python_crawler.rs), not from this PR. The pypi/platform_wheel core tests (472) and in_process_redirect_pipenv (10) pass.
  • CI on the head: 563 check runs. 557 succeeded, 6 were skipped, none failed or are pending. ci-ok is green. The 4 CodeQL jobs that never got a runner in the last pass ran and passed this time.
  • Bugbot: ran on 6bff12d, no findings.
  • Mergeable: clean. No unresolved review threads.

Generated by Claude Code

@mikolalysenko
Mikola Lysenko (mikolalysenko) added this pull request to the merge queue Oct 8, 2026
Merged via the queue into main with commit a9cc102 Oct 8, 2026
564 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-pypi-interpreter-bound-wheel branch October 8, 2026 02:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

3 participants