Repository navigation
After a hosted or vendored PDM rollback, pdm sync / pdm install keep the patched build installed, though rollback says the next install restores it #477
Description
Activity
- addedbugSomething isn't workingSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentFound by a scheduled package-manager bug-hunt agentpm:pdmPDMPDM
on Oct 1, 2026 mikolalysenko commented
on Oct 1, 2026 CollaboratorAuthorMore actions[agent] One more cell: PDM 1.4.5 (lock_version 2, legacy
[metadata.files], PEP 582__pypackages__) behaves the same way. Hosted scan, thenpdm syncinstalls the patched bytes, thenrollbackrestores the lock byte for byte (hosted.reverted: [pkg:pypi/urllib3@1.26.18]). After that,pdm syncprints🎉 All complete!and__pypackages__/3.8/lib/urllib3/response.pyis still patched. So this covers PDM 1.4.5, 2.12.4 and 2.29.2.
Generated by Claude Code
mikolalysenko commented
on Oct 1, 2026 CollaboratorAuthorMore actionsmikolalysenko commented
on Oct 1, 2026 CollaboratorAuthorMore actions[agent] New information from the scheduled uv bug-hunt routine (ledger #310): uv has the same defect, with the same root cause (the generic
reinstall_requirednote incrates/socket-patch-cli/src/commands/rollback.rs, around lines 1714–1723 on61cfb9b). Adding it here instead of filing a duplicate.After a hosted
rollbackor a vendoredrollback/vendor --reverton a uv project, the lock files are restored byte for byte. But uv treats the same-version URL/file install as satisfying the registry pin, so the patched wheel stays installed (direct_url.jsonstill points at the patch server, or at the deleted.socket/vendor/pypi/<uuid>/…whl).uv project (
uv.lock), Linux, main61cfb9b, six 1.16.0 from a local mock patch API. Each row reproduced at least twice:uv hosted rollback → uv sync --locked/--frozen/ plainuv syncvendored revert → same uv sync --reinstall-package six0.2.37, 0.4.30, 0.5.31, 0.8.17 still patched ("Audited 2 packages") still patched (0.8.17) upstream restored 0.8.18, 0.8.19, 0.8.20, 0.8.22, 0.8.24, 0.9.0, 0.10.0, 0.11.0, 0.12.0, 0.12.21 upstream reinstalled ( + six==1.16.0)upstream reinstalled (0.12.21) – So for
uv.lockprojects, uv fixed this on its side in 0.8.18. Every uv from 0.2.35 (native locks) through 0.8.17 is affected.uv requirements lane (
uv pip sync/uv pip install -r requirements.txt): after the unwind restoressix==1.16.0with its hashes, the patched wheel stays installed on every OS and version probed. That covers ubuntu, macos and windows × uv 0.1.44 / 0.5.31 / 0.12.21, hosted and vendored, LF and CRLF (12/12 cells per OS, 36 in total; probe run https://github.com/SocketDev/socket-patch/actions/runs/36923586580). uv reports "Audited/Checked 2 packages". Onlyuv pip install --reinstall sixor a fresh venv restores upstream. In those same cells the forward direction (fresh install is patched) and the byte-identical unwind both pass.Two more details:
socket-patch vendor --revert(the vendored-only unwind) prints no advisory at all: JSONevents: [{action: removed}], nowarnings.rollbackat least prints the (inaccurate) note.- After the unwind,
vexexits 2 (manifest_not_found), so nothing in socket-patch can see the leftover patched install.
Repro (uv 0.8.17):
printf '[project]\nname="app"\nversion="1.0.0"\nrequires-python=">=3.8"\ndependencies=["six==1.16.0","idna==3.6"]\n' > pyproject.toml uv lock && uv sync socket-patch scan --mode hosted --yes <api flags>; uv sync --frozen # six patched socket-patch rollback --yes <api flags> # "Note: … until the next package-manager install." git diff --exit-code uv.lock pyproject.toml # pristine uv sync --locked; uv sync --frozen; uv sync # "Audited 2 packages" python -c 'import six; print(getattr(six,"SOCKET_PATCHED",0))' # 1 <- still patched uv sync --reinstall-package six # only this restores upstream
A fix for the uv case would name
uv sync --reinstall-package <pkg>(uv.lock / script locks) anduv pip install --reinstall <pkg>(requirements / pylock), the same way the forward-directionredirect_pypi_stale_installnames a remedy.
Generated by Claude Code
mikolalysenko commented
on Oct 1, 2026 CollaboratorAuthorMore actions[agent] One more PDM entry point:
remove <purl>hits the same leftover-install path, and it prints no advisory at all. Rollback at least prints the (inaccurate) "until the next package-manager install" note.PDM 2.29.2, main
61cfb9b, vendored urllib3 1.26.18 (2/2 runs):scan --mode vendored→pdm sync(patched) →socket-patch remove pkg:pypi/urllib3@1.26.18 --yesexits 0 with only "Reverted vendoring for …".pdm.lockis restored byte for byte, and.socket/is gone. Thenpdm syncprintsAll complete! 0/0, anddirect_url.jsonstill points at the deleted.socket/vendor/pypi/<uuid>/…whlwith the patched bytes installed. A fix for #477 should coverremove(andvendor --revert, per the uv comment above), not onlyrollback.
Generated by Claude Code
- added a commit that references this issue
on Oct 1, 2026 mikolalysenko commented
on Oct 7, 2026 CollaboratorAuthorMore actions[agent] New information from the scheduled Pipenv bug-hunt routine (ledger #313): Pipenv has the same defect. The cause is the same: the generic
reinstall_requirednote incrates/socket-patch-cli/src/commands/rollback.rs(~1636). I'm adding it here instead of filing a duplicate.Pipenv never reinstalls a release that is already installed. pipenv-compatibility.md documents this for the forward direction ("Warm virtualenvs are never reinstalled"), and the scan's
redirect_pypi_stale_install/pypi_pipenv_stale_installwarnings name a Pipenv remedy for it. Rollback has no equivalent, so after an unwind the patched wheel stays installed for good.Linux, main
9c43dfc, six 1.16.0 from a local mock patch API. Steps:scan --mode hosted|vendored→pipenv sync(PATCHED) →socket-patch rollback --yes. Pipfile.lock is restored byte for byte, and rollback printsNote: 1 unwired package keeps its patched bytes in installed trees until the next package-manager install.Thenpipenv syncandpipenv install --deployboth exit 0, and six is still patched. Each cell ran 2/2:Pipenv hosted rollback → sync / --deploy vendored rollback → sync / --deploy 2022.12.19 still patched still patched 2023.12.1 still patched still patched 2026.8.0 still patched still patched remove pkg:pypi/six@1.16.0(hosted and vendored) andvendor --revertgive the same result, and they print no advisory at all.- The remedy that works is the one scan already prints:
pipenv run pip uninstall -y six && pipenv sync. That restores upstream on 2026.8.0, and so doespipenv --rm && pipenv sync. - Agent → vendored variant:
scan --mode agentpatches the venv in place, thenscan --mode vendoredmoves the manifest record into the vendor ledger ("1 manifest record moved to the vendor ledger"). After that,rollbackreverts the lock and GCs.socket/blobs, but the venv's in-place agent edit stays (PATCHED, on 2022.12.19 and 2026.8.0). Pipenv won't overwrite it, and socket-patch has no record or before-blob left to undo it. The agent → hosted variant, by contrast, keeps the manifest record, so rollback restores the venv to upstream.
A fix for Pipenv would print the same remedy as the forward warning:
pipenv run pip uninstall -y <pkg> && pipenv syncwith the lock's--dev/--categoriesarguments, orpipenv --rm && pipenv sync.
Generated by Claude Code
- addedv5-blockerMust resolve before v5: public interface/migration or ordinary patch-install-undo failure.Must resolve before v5: public interface/migration or ordinary patch-install-undo failure.uxCLI commands, help, diagnostics, output consistency, or actionable recovery instructions.CLI commands, help, diagnostics, output consistency, or actionable recovery instructions.and removed
on Oct 8, 2026 mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actionsv5 release blocker (P1). The normal rollback reinstall instruction is ineffective for PDM/Pipenv. Print the actual reinstall command needed to restore the upstream bytes.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actions[agent] Claiming for v5 blocker burn-down (shared root cause: generic reinstall_required rollback note ignores that PDM/Pipenv/uv keep same-version URL/file installs). Branch: agent/v5-pypi-rollback-reinstall. Claim-ID: 2026-10-09T16:41:34Z-dd0131
- added 2 commits that reference this issue
on Oct 9, 2026
[agent] Found by the scheduled PDM bug-hunt routine (ledger #312).
Summary
socket-patch rollbackon a hosted or vendored PDM project restorespdm.lockbyte for byte, and then prints:(JSON:
warnings[]reinstall_required, with the same text.)On PDM that isn't true. The installed copy is the same version as the restored lock entry, so PDM treats it as up to date:
pdm sync,pdm installandpdm install --frozen-lockfileall reportAll complete! 0/0and keep the patched build. Itsdirect_url.jsonstill points at the patch-server URL, or at the now-deleted.socket/vendor/pypi/<uuid>/…whl. Onlypdm sync --reinstall, or a fresh venv, brings back the upstream bytes.PDM's synchronizer only reinstalls a same-version package when the locked candidate is a URL/file that differs from the installed one. That's why the forward direction (registry → hosted/vendored) installs the patch on 2.12+/2.29, and the reverse direction (URL/file → registry) never does.
Impact
.socket/is gone, sovexandlisthave nothing to report, and a report-onlyscanjust offers the patch again.redirect_pdm_stale_install_riskfor PDM < 2.11, and the Python stale-install guard in CLI_CONTRACT "Python stale-install guard"). The rollback direction has no equivalent, and this reverse case affects every PDM version tested, including the latest.Repro (Linux, real PDM, local mock of the patch API serving a urllib3 1.26.18 wheel whose
urllib3/response.pycarries a marker line)(Env used:
SOCKET_PATCH_SERVER_URL=<mock>, and for the hosted restoreSOCKET_PYPI_JSON_API=<local pass-through to pypi.org>, because the sandbox blocks the Socket hosts. No Socket token was used.)Expected vs actual
rollback, the "next package-manager install" the note promises gives back the upstream bytes. Failing that, rollback should name the command that does, the way the forward-direction guards do (CLI_CONTRACT "Python stale-install guard": "Reinstall from the rewritten lock in the affected interpreter…"), e.g.pdm sync --reinstall(orpdm sync --reinstall <pkg>). docs/testing/pdm-compatibility.md only says "rollback… restores the pristine lock", and the backtest asserts only the lock bytes.OS × version (Linux, main
6e7ef74)pdm sync/install --frozen-lockfilepdm sync --reinstallmacOS and Windows weren't probed (no probe branch this run). PDM's same-version rule lives in its synchronizer, not in anything OS-specific, so I expect the same result there.
Suspect code
crates/socket-patch-cli/src/commands/rollback.rs:1677-1686: the genericreinstall_requirednote ("until the next package-manager install") is emitted for every ecosystem. PDM (and possibly other Python installers that skip same-version installs) needs the explicit reinstall command, or a probe of the installed copy like the forward-direction stale-install guard does.Backlog review — 2026-10-08
Priority: P1 → P2. PDM reinstall advice after rollback is wrong; a forced clean reinstall is available. Keep as a functional rollback UX issue.