Skip to content

Vendored Poetry repair and vendor re-run can't rebuild a deleted wheel on a lock-only checkout because they fetch PyPI by the patched wheel's hash #380

Description

[agent] Found by the scheduled Poetry bug-hunt routine (ledger #311).

Summary

After socket-patch vendor on a Poetry project, poetry.lock points at .socket/vendor/pypi/<uuid>/<wheel> and its files = [...] hash is the patched wheel's sha256. If the vendored wheel is then missing on a checkout with no installed copy (a fresh clone with no venv, CI, or someone deleted the wheel), socket-patch repair fails:

vendor_artifact_unrepairable: no PyPI release file for [email protected] matches the lockfile's sha256 730669e84c15…

socket-patch vendor re-run in the same state skips with vendor_fetch_unverifiable and package_not_installed.

The vendor ledger (.socket/vendor/state.json, wiring[].original) still holds the pristine lock fragment with the real PyPI hash sha256:8abb2f1d…. fetch_pristine_package was written to fall back to that fragment. It never does for Poetry, because the Poetry lock inventory reports the rewired entry as a fetchable registry resolution whose integrity is the patched hash.

uv on the same fixture rebuilds the wheel (rebuilt, exit 0). Pipenv's inventory handles this correctly: it maps Socket's own references to LockIntegrity::None, so they fall through to the ledger (lock_inventory/pypi.rs:478-495).

Impact

A vendored Poetry project can't self-heal a missing or corrupt vendored wheel unless the package also happens to be installed. poetry install then fails because the file source is gone. The error message also blames PyPI and the lockfile. CLI_CONTRACT.md reserves vendor_artifact_unrepairable for "not installed + lockfile rewired + no recoverable ledger fragment", but a recoverable fragment exists here.

Repro (Linux, Poetry 2.3.3; the synthetic manifest needs no API key)

mkdir demo && cd demo
cat > pyproject.toml <<'EOF'
[tool.poetry]
name = "demo"
version = "0.1.0"
description = ""
authors = ["x <x@x>"]
package-mode = false
[tool.poetry.dependencies]
python = "^3.8"
six = "1.16.0"
EOF
poetry lock
# .socket/manifest.json with one patch for pkg:pypi/[email protected] (six.py before/after git-sha256)
# plus .socket/blobs/<afterHash>. Any real six patch works.
socket-patch vendor --json            # success; lock rewired to the vendored wheel
POETRY_VIRTUALENVS_IN_PROJECT=true poetry install --no-root   # six.py is patched ✔
rm -rf .venv .socket/vendor/pypi/*/six-1.16.0-py2.py3-none-any.whl
socket-patch repair --json            # ❌ failed / vendor_artifact_unrepairable
socket-patch vendor --json            # ❌ skipped / vendor_fetch_unverifiable + package_not_installed

In the sandbox, SOCKET_PYPI_JSON_API pointed at a local HTTP forwarder for pypi.org, because rustls doesn't trust the sandbox proxy CA. With the same forwarder, the initial lock-only vendor fetch and the uv repair both succeed, so the fetch path works.

Expected vs actual

  • Expected: repair recovers the pre-vendor registry resolution from the ledger's wiring[].original fragment (vendor.rs doc for PristineFetch: "the ledger-recovered pre-vendor registry fragment … only --revert's restore data still knows the registry resolution"), fetches six-1.16.0-py2.py3-none-any.whl by sha256:8abb2f…, rebuilds, and reports rebuilt. CLI_CONTRACT.md only allows vendor_artifact_unrepairable when there is "no recoverable ledger fragment". uv does exactly this.
  • Actual: repair queries PyPI for the patched hash, finds nothing, and fails with exit 1. A vendor re-run can't rebuild either.

Matrix (Linux, main f6b7fb9)

Poetry lock-version vendor + poetry install repair (lock-only, wheel deleted) vendor re-run (same state)
1.1.15 1.1 ✅ patched ❌ unrepairable ❌ fetch_unverifiable
1.2.2 1.1 ✅ patched ❌ unrepairable ❌ fetch_unverifiable
1.8.5 2.0 ✅ patched ❌ unrepairable ❌ fetch_unverifiable
2.0.1 2.1 ✅ patched ❌ unrepairable ❌ fetch_unverifiable
2.3.3 2.1 ✅ patched ❌ unrepairable (reproduced 3×, across 2 runs) ❌ fetch_unverifiable
2.4.3 2.1 ✅ patched ❌ unrepairable ❌ fetch_unverifiable
uv (contrast) uv.lock ✅ ✅ rebuilt n/a

If an installed copy exists, repair rebuilds fine. macOS and Windows weren't probed. The defect is in platform-independent lock parsing.

First bad: not reproducible on release 4.0.0 or 3.3.0, which can't vendor a lock-only Poetry checkout at all (vendor_fetch_unverifiable at the first vendor). The bug comes with the unreleased lock-only Poetry fetch path (PyPI JSON API lookup by lock hash, SOCKET_PYPI_JSON_API, first added in #241).

Suspect code

  • crates/socket-patch-core/src/vendor/lock_inventory/pypi.rs:346-372 (inventory_poetry_lock): it takes the -none-any.whl hash from files without checking [package.source] type = "file" / url = ".socket/vendor/pypi/…". So a Socket-rewired entry gets LockIntegrity::Sha256Hex(<patched hash>) instead of LockIntegrity::None (compare the Pipfile.lock branch at pypi.rs:478-495).
  • crates/socket-patch-cli/src/commands/vendor.rs:1193-1198 (fetch_pristine_package): the (Some(e), _) => e arm prefers that "fetchable" inventory entry, so recover_lock_entry on the ledger fragment is never reached.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions