Skip to content

Hosted PDM rollback and remove fail permanently once the patched package leaves pdm.lock (pdm remove, or an upgrade to another version), and the suggested re-scan doesn't help #382

Description

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

Summary

After a hosted pdm.lock rewrite, if the patched package later leaves the lock, socket-patch rollback and socket-patch remove <purl> fail with exit 1 on every run. That happens with pdm remove urllib3, with removing the dependency that pulled it in, or with an upgrade to a different version (urllib3==1.26.19 + pdm lock). The error is:

pdm.lock: content matches neither the redirected nor the original fragment for redirect_pdm_lock_package — the file drifted; re-run `scan --mode hosted` to normalize

Following that advice doesn't help. The re-scan exits 0 with redirected: 0, because there is nothing left to redirect, and leaves .socket/vendor/redirect-state.json untouched, so the next rollback fails the same way. No CLI path clears the stale ledger entry, even though nothing Socket-related is left in the lock.

This differs from #331, where the package is still in the lock with our data (PR #375 addresses that rebase and does not cover a package that is gone), and from #379 (uv: drift from unrelated pyproject edits). The same engine refuses the uv equivalent (uv remove), so the fix probably belongs in the shared replay code rather than in the PDM rewriter.

Impact

Removing a dependency and upgrading past the patched version are the two normal ways a patch stops being needed, and both leave the project with a rollback/remove that can never succeed. CI that runs socket-patch rollback (or a remove in a cleanup script) fails permanently until someone deletes .socket/vendor/redirect-state.json by hand. VEX is fine: it omits the package with redirect_unwired.

Repro (Linux, real PDM 2.29.2, local mock patch API serving a real patched urllib3 1.26.18 wheel; no Socket token)

API="--api-url http://127.0.0.1:8765 --api-token fake --org test"
printf '[project]\nname = "proj"\nversion = "0.1.0"\nrequires-python = ">=3.8"\ndependencies = ["urllib3==1.26.18", "six"]\n[tool.pdm]\ndistribution = false\n' > pyproject.toml
pdm lock
socket-patch scan --mode hosted --json --yes --ecosystems pypi $API   # redirected: 1
pdm remove --no-sync urllib3          # or: sed -i s/1.26.18/1.26.19/ pyproject.toml && pdm lock
socket-patch rollback --json --yes $API                               # exit 1, partial_failure, "the file drifted; re-run scan"
socket-patch scan --mode hosted --json --yes --ecosystems pypi $API   # exit 0, redirected: 0
socket-patch rollback --json --yes $API                               # exit 1 again, same error
socket-patch remove pkg:pypi/[email protected] --json --yes $API        # exit 1, hosted_revert_failed, same message
ls .socket/vendor/redirect-state.json                                 # still present

The mock implements /v0/orgs/<org>/patches/{batch,by-package,view,package} and serves the wheel, modeled on crates/socket-patch-cli/tests/vex_pdm_hatch_common/mod.rs::ScanApi.

Control: a plain relock that keeps urllib3 at 1.26.18 (pdm lock → re-scan → rollback) succeeds, as documented.

Expected vs actual

  • Expected: when the lock no longer contains the redirected package, or holds it at a different version with no Socket url/hash, nothing is left to unwind. Rollback/remove should drop that ledger edit and succeed, or at least the re-scan the error recommends should prune it. docs/testing/pdm-compatibility.md ("Mode notes") says rollback (or remove <purl>) "restores the pristine lock and drops the ledger/manifest state for all three modes". The drift message promises that a re-scan normalizes the state.
  • Actual: exit 1 on every rollback/remove, and the re-scan never repairs the ledger.

OS × version

PDM lock_version pdm remove upgrade to 1.26.19 transitive removal (pdm remove requests)
2.12.4 (Linux) 4.4.1 fail (2/2) untested untested
2.20.1 (Linux) 4.5.0 fail (2/2) fail untested
2.29.2 (Linux) 4.5.1 fail (2/2) fail fail

macOS/Windows: not probed, because the logic is platform-independent. The same flow with uv (uv remove) also fails, in redirect_uv_lock_wheel.

First bad version: not bisected. The published 4.0.0 wheel doesn't produce a hosted redirect for this lock-only project against the mock, so there is nothing to compare. Tested on main f6b7fb9.

Suspect code

  • crates/socket-patch-core/src/patch/redirect/replay.rs:803-811: the fall-through refusal has no case for "the recorded new is absent and the package/version no longer exists in the lock".
  • crates/socket-patch-cli/src/hosted_memory/ledger.rs (rebase on re-scan): a re-scan that produces no edit for a recorded package keeps the stale edit instead of pruning it.

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