Skip to content

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

Closed

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:

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 --check verifies. CLI_CONTRACT.md: drift-keep is for "the lock changed under us" (a third-party edit), with the remedy "re-run scan --mode vendored to 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: rollback after the 1.8 → 2.5 re-lock restores the registry entries and the next poetry install is unpatched.
  • Actual: permanent drift-keep. vendor --revert exits 0 while rollback / remove exit 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

No probe runs: macOS / Windows probe branches are blocked (ledger #311).

Activity

  1. mikolalysenko commented on Oct 16, 2026

    @mikolalysenko
    CollaboratorAuthor

    [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 --yes exits 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 --revert lists it)". That remedy is the --revert that drift-keeps above. The project stays vendored and patched (a fresh poetry install imports the patched six), so this is fail-safe, but it can't move to hosted without hand-editing poetry.lock.


    Generated by Claude Code

  2. added a commit that references this issue on Oct 16, 2026
  3. mikolalysenko commented on Oct 17, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triage: priority:p1 (Poetry). Confirmed on current main: revert_poetry (vendor/pypi_poetry.rs) routes through revert_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

  4. mikolalysenko commented on Oct 17, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Same failure when the vendoring happens on a legacy lock-1.1 file, on main 657fce2 (2/2 runs each):

    Vendored with Re-locked with Lock after vendor --revert rollback wiring left
    Poetry 1.1.15 (lock 1.1, [package.source] + [metadata.files] fragments) 1.8.5 poetry lock --no-update 2.0 exit 0, vendor_lock_entry_drifted ×2 + artifact kept exit 1, vendor_revert_kept yes
    Poetry 1.1.15 2.5.1 poetry lock 2.1 exit 0, same events exit 1, vendor_revert_kept yes
    Poetry 1.1.15 1.1.15 poetry lock --no-update (control) 1.1 exit 0, restored n/a no

    Both re-locks keep [package.source] type = "file" and a fresh poetry install still 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-editing poetry.lock.

    # Poetry 1.1.15 project pinning six==1.16.0 + idna==3.7
    poetry@1.1.15 lock && socket-patch scan --mode vendored --yes
    poetry@2.5.1 lock                      # or poetry@1.8.5 lock --no-update
    socket-patch vendor --revert --yes --json   # exit 0, skipped/vendor_lock_entry_drifted, lock still wired
    socket-patch rollback --yes --json          # exit 1, vendor_revert_kept

    Generated by Claude Code

  5. mikolalysenko commented on Oct 17, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Poetry bug-hunt run 28, main 7d3086f (no code change since 657fce2). This also reproduces without any lock-version change: lock 2.0 → lock 2.0, Poetry 1.3.2 → 1.8.5.

    1. Lock with Poetry 1.3.2 (lock-version = "2.0", unversioned @generated stamp, category = "main").
    2. socket-patch scan --mode vendored, then poetry lock --no-update with Poetry 1.8.5.
    3. socket-patch vendor --revert --json exits 0 with "status": "success", but the events are vendor_lock_entry_drifted ("poetry.lock fragment for Some("six") changed since vendoring"), vendor_artifact_kept and vendor_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

  6. mikolalysenko commented on Oct 22, 2026

    @mikolalysenko
    CollaboratorAuthor

    [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-b43031


    Generated by Claude Code

  7. mikolalysenko commented on Oct 22, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft fix PR: #1662


    Generated by Claude Code

  8. added a commit that references this issue on Oct 22, 2026
    1802052
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions