Repository navigation
Hosted PDM rollback reports success but leaves the patch url/hash in pdm.lock after pdm add or pdm lock --update-reuse and a re-scan #331
Description
[agent] Found by the scheduled PDM bug-hunt routine (ledger #312).
Summary
After a hosted pdm.lock rewrite, some PDM commands re-render the urllib3 block but keep our patch data:
pdm add <other>andpdm lock --update-reuseon PDM 2.26 and 2.29 keep the patchurland the patched hash.- On PDM 2.12 – 2.20,
pdm adddrops theurlbut keeps the patchedfileshash.
The first rollback then fails closed with a drift error that says to "re-run scan --mode hosted to normalize". After that re-scan, rollback reports success, lists reverted: ["pkg:pypi/urllib3@1.26.18"] and deletes the redirect ledger. But pdm.lock still carries the patch data:
- PDM 2.26 / 2.29: the lock keeps the hosted
urland the patched sha256. The project stays silently hosted-patched, with no ledger left, so no CLI path back to the registry lock remains. - PDM 2.12 / 2.20: the lock keeps the patched sha256 with no
url, i.e. a registry source pinned to the patched wheel's hash.pdm syncthen fails (InstallationError: Some package operations failed). Rollback leaves an uninstallable lock and reports success.
The re-scan treats the still-patched block as the pristine original and records it as the edit's original fragment. So the rebase is pristine → pristine instead of pristine → current.
Impact
Rollback silently lies. CI or a user who runs rollback to un-patch believes the project is back on upstream bytes. It is not, and on 2.12 – 2.20 it no longer installs at all. The doc's guarantee is broken: docs/testing/pdm-compatibility.md:64-67 says "A re-scan after a relock re-applies the patch and rebases the ledger's recorded edits onto the relocked text (pristine → current…), so rollback still lands on the pristine lock afterwards."
Repro (Linux, real PDM, mock patch API serving a real patched wheel)
printf '[project]\nname="proj"\nversion="0.1.0"\nrequires-python=">=3.8"\ndependencies=["urllib3==1.26.18"]\n[tool.pdm]\ndistribution=false\n' > pyproject.toml
pdm lock
socket-patch scan --mode hosted --json --yes $API # redirected 1
pdm add six==1.16.0 # keeps url= (2.29) / keeps patched hash only (2.20)
socket-patch rollback --json --yes $API # partial_failure: "content matches neither ... re-run `scan --mode hosted` to normalize"
socket-patch scan --mode hosted --json --yes $API # success, redirected 1
socket-patch rollback --json --yes $API # success, reverted [pkg:pypi/urllib3@1.26.18], editedFiles 1
grep -c '^url = ' pdm.lock # 1 on 2.29.2 (expected 0)
grep -c 1497f1666ebc pdm.lock # 1 = patched wheel sha256 still pinned (expected 0)
ls .socket/vendor/redirect-state.json # gone
pdm sync # 2.29.2: installs the PATCHED urllib3; 2.12.4/2.20.1: InstallationError($API = --api-url http://127.0.0.1:<port> --api-token fake --org test against a local mock of /patches/batch, /patches/view, /patches/package. The mock serves an urllib3 1.26.18 wheel with a marker line appended to urllib3/response.py. No Socket token was used.)
Reproduced on 2 of 2 fresh runs on 2.29.2, and also without the intermediate failed rollback. A plain pdm lock, which re-resolves to the registry, is not affected: rollback lands on the relocked registry block there, as documented.
Expected vs actual
- Expected: after
scan→ relock-style edit →scan→rollback, the urllib3 block has no Socketurland the upstreamfileshashes, per the doc quoted above. Otherwise rollback should fail closed rather than reportsuccess. - Actual:
status: success,reverted: [purl], ledger removed, and the patchedurl/hash left inpdm.lock.
Matrix (Linux; the rewrite is platform-independent text handling)
| PDM | post-rewrite command | after re-scan + rollback | pdm sync afterwards |
|---|---|---|---|
| 2.29.2 | pdm add six==1.16.0 |
url + patched hash remain | installs patched |
| 2.29.2 | pdm lock --update-reuse |
url + patched hash remain | installs patched |
| 2.29.2 | pdm lock |
clean (registry) ✅ | upstream |
| 2.26.9 | pdm add six==1.16.0 |
url + patched hash remain | installs patched |
| 2.20.1 | pdm add six==1.16.0 |
patched hash, no url | fails |
| 2.12.4 | pdm add six==1.16.0 |
patched hash, no url | fails |
macOS and Windows were not run for this one. The defect is in lock-text handling, which has no OS-specific code path.
Suspect code
crates/socket-patch-core/src/utils/pdm_lock.rs:383-399:plan_pdm_rewriteaccepts an existing prior-hostedurl(is_prior_hosted_url), thenpdm_lock_fragments_inat:266records that already-redirected block as thebefore/original fragment. A block whosefileshash equals the patched artifact's sha256 (no url) is likewise taken as pristine.- The rebase (pristine → current) for re-scans of hosted PDM edits should keep the ledger's earlier original when the current fragment still carries our URL or our artifact hash.
Tested on main f6b7fb9 (latest release v4.0.0).
Activity
mikolalysenko commentedon Sep 30, 2026
[agent] Triaged:
priority:p1(PDM). Not a duplicate, and no existing fix PR. The cause is inutils/pdm_lock.rs: a re-scan records a block that is already hosted (oururl, or our artifact sha256 with no url) as the pristineoriginalfragment, when it should keep the ledger's earlier original.Generated by Claude Code
mikolalysenko commentedon Sep 30, 2026
[agent] Claiming this issue (shared root cause: the hosted ledger's PDM rebase always adopts the re-scan's
originalfragment, even when that fragment still carries Socket's url or patched hash). Branch: agent/fix-pdm-rebase-keeps-pristine. Claim-ID: 2026-09-30T20:20:37Z-fb6405Generated by Claude Code
mikolalysenko commentedon Sep 30, 2026
[agent] Draft PR: #375
Generated by Claude Code
Add tests for PDM rollback after pdm add re-scan
Keep PDM's pristine unit when a relock keeps the patch
Match real PDM 2.29.2 re-render in the #331 test
mikolalysenko commentedon Oct 1, 2026
[agent] Re-triage (PDM bug-hunt, ledger #312): this no longer reproduces on main
2463257(#277, v5). v5 hosted mode keeps no ledger, and rollback restores the pin from PyPI metadata instead of replaying a recorded fragment.Tested on Linux with real PDM against the mock patch API:
pdm add six→ re-scan → rollbackpdm syncOKpdm syncOKpdm lock --update-reuse→ re-scan → rollbackpdm syncandpdm lock --checkOK"Refs" counts lines in
pdm.lockmatching the hosted URL or the patched wheel sha256. Closing as fixed by #277. PR #375 targets the removed v4 ledger path.Generated by Claude Code