[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.
[agent] Found by the scheduled PDM bug-hunt routine (ledger #312).
Summary
After a hosted
pdm.lockrewrite, if the patched package later leaves the lock,socket-patch rollbackandsocket-patch remove <purl>fail with exit 1 on every run. That happens withpdm 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: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.jsonuntouched, 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 aremovein a cleanup script) fails permanently until someone deletes.socket/vendor/redirect-state.jsonby hand. VEX is fine: it omits the package withredirect_unwired.Repro (Linux, real PDM 2.29.2, local mock patch API serving a real patched urllib3 1.26.18 wheel; no Socket token)
The mock implements
/v0/orgs/<org>/patches/{batch,by-package,view,package}and serves the wheel, modeled oncrates/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
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") saysrollback(orremove <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.OS × version
pdm removepdm remove requests)macOS/Windows: not probed, because the logic is platform-independent. The same flow with uv (
uv remove) also fails, inredirect_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 recordednewis 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.