Repository navigation
Vendored Poetry patches can't be reverted after a poetry lock from another Poetry major (1.8 ⇄ 2.x): vendor --revert drift-keeps every package with exit 0, and the "re-vendor" remedy is a no-op #1615
Description
[agent] Found by the scheduled Poetry bug-hunt routine (ledger #311).
Summary
Vendored Poetry wiring is reverted by splicing the recorded [[package]] fragment back verbatim (revert_lock_fragment_splice_atomic). When a team upgrades Poetry and re-locks, Poetry 2.x re-renders every [[package]] unit and adds groups = ["main"]. Going the other way, Poetry 1.8 drops groups / markers again. The vendored wiring survives the re-lock intact: same [package.source] file URL, same patched hash, and vendor --check reports "committed artifact and wiring verified". But the unit no longer matches the recorded text byte for byte. So:
vendor --revertkeeps every package (vendor_lock_entry_drifted→vendor_revert_kept), leavespoetry.lockvendored, and still exits 0 withstatus: success.rollbackandremoveexit 1 with the same drift-keep.- The remedy all three print, "undo the drift (restore the vendored lock entries or re-vendor)" / "re-run
scan --mode vendoredto normalize", does nothing.scan --mode vendoredsays "All 2 packages are already vendored; nothing to do." and the next--revertdrift-keeps again. This is the Poetry form of Vendored requirements.txt can't be reverted once the user touches the# socket-patch vendor:comment:vendor --revertdrift-keeps forever (exit 0), and the suggested "re-vendor" remedy is a no-op or adds a duplicate line #977 (requirements.txt).
Impact
Upgrading Poetry 1.x → 2.x and running poetry lock is routine. After it, socket-patch can never remove a vendored Poetry patch: not with vendor --revert, rollback or remove, and not after re-vendoring. The only way out is to hand-edit poetry.lock, or git checkout the pre-vendor lock, which throws the re-lock away. vendor --revert reports success while nothing was reverted, so CI scripts that rely on its exit code carry on with the patched wheel still wired. The same happens after a 2.x lock is re-locked by a teammate still on Poetry 1.8.
Repro (Linux, main 657fce2; any vendorable PyPI patch; six + idna here)
mkdir demo && cd demo
cat > pyproject.toml <<'EOF'
[tool.poetry]
name = "demo-up"
version = "0.1.0"
description = ""
authors = ["x <x@example.com>"]
package-mode = false
[tool.poetry.dependencies]
python = "^3.9"
six = "1.16.0"
idna = "3.7"
EOF
poetry-1.8.5 lock # lock-version 2.0
socket-patch scan --mode vendored --yes
poetry-2.5.1 lock # lock-version 2.1; only adds `groups = ["main"]` to each unit
socket-patch vendor --check # "committed artifact and wiring verified" (both)
socket-patch vendor --revert --yes; echo $? # "Kept 2 drifted packages ...", exit 0
grep -c socket/vendor poetry.lock # 2: still vendored
socket-patch scan --mode vendored --yes # "already vendored; nothing to do"
socket-patch vendor --revert --yes # drift-keeps again, exit 0
socket-patch rollback --yes; echo $? # exit 1, "lockfile wiring drifted"
socket-patch remove pkg:pypi/six@1.16.0 --yes; echo $? # exit 1, "re-run `scan --mode vendored` to normalize"After the Poetry 2.5.1 re-lock, the six unit differs from what socket-patch wrote only by the header, lock-version, and this added line:
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+groups = ["main"]
files = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:9fc57c9c…"},
]
[package.source]
type = "file"
url = ".socket/vendor/pypi/3c5e7a9b-…/six-1.16.0-py2.py3-none-any.whl"vendor --revert --json events (per package): skipped / vendor_lock_entry_drifted ("poetry.lock fragment for Some("idna") changed since vendoring; left untouched"), then vendor_artifact_kept, then vendor_revert_kept. Top-level status: "success", warnings: [].
Expected vs actual
- Expected: the revert restores the registry entries whenever the live unit still carries the vendored wiring that
vendor --checkverifies. CLI_CONTRACT.md: drift-keep is for "the lock changed under us" (a third-party edit), with the remedy "re-runscan --mode vendoredto normalize". A Poetry re-render that keeps the wiring isn't a third-party change to it, and if it were, re-vendoring would have to normalize it. Hosted mode handles the same upgrade fine:rollbackafter the 1.8 → 2.5 re-lock restores the registry entries and the nextpoetry installis unpatched. - Actual: permanent drift-keep.
vendor --revertexits 0 whilerollback/removeexit 1, and the suggested remedy is a no-op.
OS × version
| Vendored with | Re-locked with | Result |
|---|---|---|
| Poetry 2.5.1 | Poetry 2.5.1 (lock) |
pass (reverted, 0 refs left) |
| Poetry 1.8.5 | Poetry 1.8.5 (lock --no-update) |
pass |
| Poetry 1.8.5 | Poetry 2.5.1 (lock) |
fail 2/2 on main, 2/2 on release v5.0.1 |
| Poetry 1.8.5 | Poetry 2.4.3 (lock) |
fail |
| Poetry 2.5.1 | Poetry 1.8.5 (lock --no-update) |
fail |
| macOS / Windows | not run (probe branches are blocked, see ledger #311). The comparison is plain string matching, so it's OS-independent. |
First bad: not a regression. Release v5.0.1 behaves the same.
Workaround: git checkout -- poetry.lock back to the pre-vendor lock, then socket-patch vendor --revert converges ("already restored"). Then re-lock again.
Suspect code
crates/socket-patch-core/src/vendor/common.rs:805:replace_fragment(&lock_text, new_text, original_text)needs the recordednewfragment verbatim. Otherwise it's drift, unless the original is already present (line ~817). For Poetry, a semantic match would avoid this: the samename/version,[package.source]typefilewith the recorded.socket/vendor/pypi/<uuid>/…URL, and the recorded patched hash infiles. Then restore the original unit, keeping fields Poetry added (groups/markers) or re-rendering for the live lock-version, as the hosted rollback path already does.crates/socket-patch-core/src/vendor/pypi_poetry.rs:430(revert_poetry) routes Poetry through that splice.- The
vendor --revertexit code /statuson a full drift-keep is the same gap Vendored requirements.txt can't be reverted once the user touches the# socket-patch vendor:comment:vendor --revertdrift-keeps forever (exit 0), and the suggested "re-vendor" remedy is a no-op or adds a duplicate line #977 describes for requirements.txt.removealready exits 1 there.
No probe runs: macOS / Windows probe branches are blocked (ledger #311).
Activity
mikolalysenko commentedon Oct 16, 2026
[agent] One more path is stuck by the same re-lock: a vendored → hosted takeover. After vendoring with Poetry 1.8.5 and re-locking with 2.5.1,
scan --mode hosted --yesexits 0 with "Switched 0 packages to hosted patches". For each package it warns "part of its vendored wiring was edited since vendoring, so it is left in place; NOT switched to hosted — restore or remove that wiring (socket-patch vendor --revertlists it)". That remedy is the--revertthat drift-keeps above. The project stays vendored and patched (a freshpoetry installimports the patched six), so this is fail-safe, but it can't move to hosted without hand-editingpoetry.lock.Generated by Claude Code
poetry: run 26 ledger (#1615, #1589 comment, pip/pdm handovers)
mikolalysenko commentedon Oct 17, 2026
[agent] Triage:
priority:p1(Poetry). Confirmed on currentmain:revert_poetry(vendor/pypi_poetry.rs) routes throughrevert_lock_fragment_splice_atomic(vendor/common.rs), which needs the recorded[[package]]fragment byte for byte, so a Poetry re-render that keeps the wiring is treated as drift. Related to #977 (same user-visible exit-code/remedy gap), but that one goes through the requirements.txt line reverter, a different code path, so not clustered. Also in scope of the #989 record-revert engine tracker. No duplicate or existing fix PR found.Generated by Claude Code
mikolalysenko commentedon Oct 17, 2026
[agent] Same failure when the vendoring happens on a legacy lock-1.1 file, on main
657fce2(2/2 runs each):vendor --revertrollback[package.source]+[metadata.files]fragments)poetry lock --no-updatevendor_lock_entry_drifted×2 + artifact keptvendor_revert_keptpoetry lockvendor_revert_keptpoetry lock --no-update(control)Both re-locks keep
[package.source] type = "file"and a freshpoetry installstill imports the patched six, so it's fail-safe. But this is the path every team upgrading from Poetry 1.1 takes (1.1 → 1.8/2.x rewrites the whole lock into the 2.x shape), so after that upgrade the project can't leave vendored mode without hand-editingpoetry.lock.Generated by Claude Code
mikolalysenko commentedon Oct 17, 2026
[agent] Poetry bug-hunt run 28, main
7d3086f(no code change since657fce2). This also reproduces without any lock-version change: lock 2.0 → lock 2.0, Poetry 1.3.2 → 1.8.5.lock-version = "2.0", unversioned@generatedstamp,category = "main").socket-patch scan --mode vendored, thenpoetry lock --no-updatewith Poetry 1.8.5.socket-patch vendor --revert --jsonexits 0 with"status": "success", but the events arevendor_lock_entry_drifted("poetry.lock fragment for Some("six") changed since vendoring"),vendor_artifact_keptandvendor_revert_kept. The lock still sources six from.socket/vendor/pypi/….The only differences in the re-rendered six block are that 1.8 drops
category = "main"and rewrites the stamp line. So any 1.3 → 1.4+ upgrade within lock 2.0 hits the drift check, not just a lock-format change. Reproduced 2/2.Generated by Claude Code
mikolalysenko commentedon Oct 22, 2026
[agent] Claiming this issue (shared root cause: the vendored Poetry revert only matches the recorded
[[package]]text byte for byte, so a re-render by another Poetry version reads as drift). Branch: agent/fix-poetry-vendored-revert-rerender. Claim-ID: 2026-10-22T04:21:12Z-b43031Generated by Claude Code
mikolalysenko commentedon Oct 22, 2026
[agent] Draft fix PR: #1662
Generated by Claude Code
Revert vendored Poetry units after a re-lock