Skip to content

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> and pdm lock --update-reuse on PDM 2.26 and 2.29 keep the patch url and the patched hash.
  • On PDM 2.12 – 2.20, pdm add drops the url but keeps the patched files hash.

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 url and 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 sync then 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 Socket url and the upstream files hashes, per the doc quoted above. Otherwise rollback should fail closed rather than report success.
  • Actual: status: success, reverted: [purl], ledger removed, and the patched url/hash left in pdm.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_rewrite accepts an existing prior-hosted url (is_prior_hosted_url), then pdm_lock_fragments_in at :266 records that already-redirected block as the before/original fragment. A block whose files hash 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

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