Skip to content

Poetry hosted ⇄ vendored mode switch is refused, and blames a "user-authored" source that socket-patch wrote itself #328

Description

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

Summary

On a Poetry project, switching from one socket-patch mode to the other by re-running scan with the new --mode doesn't work in either direction:

  • hosted → vendored (scan --mode vendored on a hosted-redirected lock):
    • with the package installed: failed / pypi_poetry_source_already_exists, "poetry.lock already declares a [package.source] for six; refusing to overwrite a user-authored source". Exit 1.
    • lock-only checkout: skipped / vendor_fetch_unverifiable, "no PyPI release file for [email protected] matches the lockfile's sha256 82a96d…" (the hash is the hosted wheel's, which socket-patch wrote), then package_not_installed. Exit 1.
  • vendored → hosted (scan --mode hosted on a vendored lock): redirected: 0, warning redirect_poetry_lock_unsupported, "poetry.lock: refusing to replace an existing Poetry source (file .socket/vendor/pypi//six-….whl) for six". "status": "success", exit 0.

In each case the [package.source] being refused is the one socket-patch wrote, and it's recorded in .socket/vendor/redirect-state.json or .socket/vendor/state.json. The takeover path in commands/vendor.rs never runs for PyPI because redirect_revert_supported only accepts pkg:cargo/, pkg:npm/ and pkg:golang/.

The lock is left intact (still patched in the old mode), so this fails closed. But the codes and messages send the user the wrong way: "user-authored source", "lock unsupported", "no PyPI release file matches". None of them says the working remedy, which is socket-patch rollback first and then the new mode (verified: rollback restores the pristine lock byte for byte, and scan --mode hosted then redirects 1).

Impact

A user moving a Poetry project between hosted and vendored mode (e.g. after adopting hosted by default in v5, or going offline-safe) gets a failing CI step (hosted → vendored) or a silent no-op that reports success (vendored → hosted). The error messages point at a nonexistent user edit. The same happens with uv (pypi_uv_source_already_exists), so this is PyPI-wide. It's filed here with the Poetry evidence.

Repro (Linux, Poetry 2.3.3; mock patch API as in tests/e2e_vex_build/poetry.rs)

# pyproject: package-mode = false; python-dateutil = "2.9.0.post0" (six 1.16.0 transitive)
poetry lock && cp poetry.lock poetry.lock.orig
export SOCKET_API_URL=http://127.0.0.1:18080 SOCKET_API_TOKEN=fake SOCKET_ORG_SLUG=test-org
socket-patch scan --mode hosted --json --yes     # redirected: 1
poetry sync                                      # installs the hosted wheel
socket-patch scan --mode vendored --json --yes; echo "exit=$?"
# -> failed pypi_poetry_source_already_exists "...refusing to overwrite a user-authored source", exit=1

cp poetry.lock.orig poetry.lock && rm -rf .socket
socket-patch scan --mode vendored --json --yes   # success
socket-patch scan --mode hosted --json --yes; echo "exit=$?"
# -> redirected: 0, warning redirect_poetry_lock_unsupported "refusing to replace an existing Poetry source (file .socket/vendor/pypi/…)", exit=0

Each direction was reproduced twice on the current main.

Expected vs actual

  • Expected: either a takeover like the one for npm, cargo, go and Bun (README "Bun compatibility": "hosted and vendored patches, mode switching, repair, and rollback"; docs/ecosystems.md: "Hosted → vendored and vendored → hosted conversions both work in place (mode takeover)"), or a dedicated refusal code that names the socket-patch-owned wiring and says to run socket-patch rollback first. On vendored → hosted, the exit status shouldn't be success while nothing was redirected.
  • Actual: the generic "user-authored source" / "unsupported lock" refusals above.

OS × version

Direction Poetry 2.3.3 Linux uv 0.x Linux (same code path)
hosted → vendored (installed) ❌ pypi_poetry_source_already_exists ❌ pypi_uv_source_already_exists
hosted → vendored (lock-only) ❌ vendor_fetch_unverifiable not tested
vendored → hosted ❌ redirect_poetry_lock_unsupported, exit 0 not tested

This is OS-independent: it's pure lock and ledger logic, with no filesystem or path handling involved.

Suspect code

  • crates/socket-patch-core/src/patch/redirect/takeover.rs:79 redirect_revert_supported: no pkg:pypi/ arm, so commands/vendor.rs (the "Cross-mode takeover" block) skips the revert.
  • crates/socket-patch-core/src/vendor/pypi_poetry.rs:250 / :260 / :308: pypi_poetry_source_already_exists doesn't check the redirect ledger before calling the source user-authored.
  • crates/socket-patch-core/src/utils/poetry_lock.rs:415 / :952: the hosted rewriter refuses the vendored type = "file" source it didn't recognise as Socket-owned.

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