[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.
[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
scanwith the new--modedoesn't work in either direction:scan --mode vendoredon a hosted-redirected lock):failed/pypi_poetry_source_already_exists, "poetry.lock already declares a [package.source] for six; refusing to overwrite a user-authored source". Exit 1.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), thenpackage_not_installed. Exit 1.scan --mode hostedon a vendored lock):redirected: 0, warningredirect_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.jsonor.socket/vendor/state.json. The takeover path incommands/vendor.rsnever runs for PyPI becauseredirect_revert_supportedonly acceptspkg:cargo/,pkg:npm/andpkg: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 rollbackfirst and then the new mode (verified: rollback restores the pristine lock byte for byte, andscan --mode hostedthen 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)Each direction was reproduced twice on the current main.
Expected vs actual
socket-patch rollbackfirst. On vendored → hosted, the exit status shouldn't be success while nothing was redirected.OS × version
pypi_poetry_source_already_existspypi_uv_source_already_existsvendor_fetch_unverifiableredirect_poetry_lock_unsupported, exit 0This 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:79redirect_revert_supported: nopkg:pypi/arm, socommands/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_existsdoesn'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 vendoredtype = "file"source it didn't recognise as Socket-owned.