Repository navigation
Fix PyPI revert deleting still-referenced wheel (#996, #867) - #997
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Add failing regression tests for a vendored PyPI revert that deletes the vendored wheel while another project file still installs it: - #996: a `uv export`-ed requirements.txt or pylock.toml after a uv project revert, and the same export after a script-lock revert. - #867: a vendored requirements line moved into a `-r` include, or into a sibling requirements file the root does not include. Assisted-by: Claude Code:claude-opus-5-5
A vendored Python revert restored the files it recorded and then deleted .socket/vendor/pypi/<uuid>/ without checking whether any other project file still installs the wheel. A uv-exported requirements.txt or pylock.toml (#996), or a vendor line moved into a -r include or sibling requirements file (#867), was left pointing at a deleted wheel, so every later install failed while the revert reported success. Every wired PyPI revert now runs the same reference probe the unwired guard uses (root requirements.txt and its includes, uv.lock, pylock and script locks, pyproject/hatch/Pipfile and now every root-level *.txt) before deleting. A remaining reference keeps the wheel and the ledger entry with vendor_revert_residual_reference naming the file, and the dry run previews the same keep. Re-running vendor --revert once the file is fixed finishes the cleanup. Assisted-by: Claude Code:claude-opus-5-5
82a409e to
66d2396
Compare
Run the real uv toolchain: vendor six, `uv export` a requirements.txt that names the vendored wheel, and check `vendor --revert` restores the uv pair but keeps the wheel and ledger entry while the export still installs from it. After re-exporting from the restored lock, a second revert removes the artifact (#996). Assisted-by: Claude Code:claude-opus-5-5
main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 65112a8)
|
BugBot review Generated by Claude Code |
|
[agent] CI note: Generated by Claude Code |
|
[agent] Ready for review at
Generated by Claude Code |
|
bugbot run |
|
[agent] CI note on a4026cc: CodeQL Generated by Claude Code |
|
[agent] Ready for review at
Generated by Claude Code |
|
BugBot review Generated by Claude Code |
|
[agent] |
pypi_reference_clause read every root *.txt as UTF-8 and failed closed on InvalidData. So an unrelated Latin-1 LICENSE.txt, or a BOM-marked UTF-16 requirements.txt from PowerShell's `pip freeze >` (which pip itself reads), kept the wheel and ledger entry on every wired revert. The way-out text didn't apply, so the user could never clear it. Search the raw bytes for the uuid-dir needle as UTF-8 or UTF-16 in either byte order, and fail closed only on real read errors. When the root requirements.txt isn't UTF-8, skip the include walk; the root-level *.txt byte scan still probes it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016w5DTe9ejKmm3mvdbi8VXE
|
[agent] Tanmay Singla (@Tanmay182003) Confirmed and fixed in 3acc3d5 (pushing now).
The commit also merges current Generated by Claude Code |
The revert's residual-reference probe skipped the whole requirements -r include walk as soon as any file in the tree was not UTF-8. A vendored line in a subdirectory include (say reqs/dev.txt with a Latin-1 comment, or a UTF-16 file PowerShell wrote) then went unprobed, and revert deleted a wheel pip still installs from. The walk now reads each file through the same decode the probe uses: UTF-16 by its BOM, anything else lossily, so every ASCII byte of the vendor path and the -r grammar survives. Only a real read error still fails closed. Assisted-by: Claude Code:claude-opus-5-5
|
[agent] Tanmay Singla (@Tanmay182003) follow-up on the non-UTF-8 finding:
Two runs worked on this finding at the same time (this run's heartbeat was refreshed at 15:22). I merged the two fixes instead of overwriting Generated by Claude Code |
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit cce2b28. Configure here.
LLM Description written by Claude Code:claude-opus-5-5
Fixes #996
Fixes #867
Summary
A vendored Python
vendor --revert,removeorrollbackrestored the files its ledger entry recorded, then deleted.socket/vendor/pypi/<uuid>/. It never checked whether another project file still installs that wheel. Any such file was left pointing at a deleted wheel, so every later install failed, while the revert reportedsuccessand exit 0.vendor --revert/remove/rollbackdelete the vendored wheel while auv export-ed requirements.txt or pylock.toml still points at it (exit 0), so installs from the exported file fail #996: auv export-edrequirements.txtorpylock.toml(uv project lane and PEP 723 script lane).vendor --revert/removedelete the vendored wheel while a-rinclude still points at it (exit 0), so every laterpip install -r requirements.txtfails #867: a vendored requirements line the user moved into a-rinclude, or into a sibling file such asrequirements-dev.txtthat the root doesn't include.Root cause
revert_pypi_opts(crates/socket-patch-core/src/vendor/pypi.rs) already has a probe for this,unwired_pypi_reference_clause. It reads every Python project file for the uuid dir: the rootrequirements.txtplus its-rincludes,uv.lock,pylock*.toml,*.py.lockwith their scripts, andpyproject.toml,hatch.toml,Pipfile*,poetry.lockandpdm.lock. But it only ran for ledger entries with no wiring. Every wired flavor went straight to deletion:Fix
pypi_reference_clause). It now also reads every root-level*.txt, which covers auv export -otarget and Vendored requirements.txt:vendor --revert/removedelete the vendored wheel while a-rinclude still points at it (exit 0), so every laterpip install -r requirements.txtfails #867's non-included sibling. Files are matched as bytes (the needle as UTF-8, UTF-16LE or UTF-16BE), so a non-UTF-8LICENSE.txtdoesn't block the revert, while a UTF-16requirements.txtthat pip reads is still probed.kept_artifact, so the CLI reportsvendor_revert_kept). It addsvendor_revert_residual_referencenaming the file and the way out: point it back at the registry release or re-export it, then re-runvendor --revert.--dry-runpreviews the same warning. It skips the files the flavor would restore, and per the existingkept_artifactcontract the keep flag itself stays wet-only.revert_keeps_wiring. It refuses a takeover that would also have broken that file.Ported fix: this PR cherry-picks 65112a8 from #878 (route the Gradle digests through
utils::digest). Without it,production_digests_go_through_the_helpersfails onmainand turnstestred on every PR. The commit no-ops once #878 lands.Test evidence
New tests (red on
mainat 3ac5183, green with the fix at 66d2396):vendor::pypi::tests::uv_revert_keeps_artifact_while_export_references_it[])uv export --script)vendor::pypi::tests::script_lock_revert_keeps_artifact_while_export_references_itno residual warning: []-rinclude and non-included siblingvendor::pypi::tests::requirements_revert_keeps_artifact_for_moved_vendor_linevendor_revert_line_drifted, wheel deletede2e_vendor_pypi_build::uv_vendor_revert_keeps_wheel_while_export_references_it(realuv export, two-step revert)Commands run locally:
cargo clippy --workspace --all-features -- -D warnings: clean.rustfmt --checkon the touched files: clean. CI doesn't runcargo fmt, andmainisn't fmt-clean workspace-wide, so I formatted only the touched files.cargo test --workspace --all-features --no-fail-fast: all pass except 12 tests that inject failures withchmod 0o555/read-only dirs (*_write_failure_*,*unremovable*,relax_loop_must_not_traverse_symlinked_root, ...). They fail here only because the sandbox runs as uid 0, which bypasses file permissions. None of them touch the revert path changed here.cargo test -p socket-patch-cli --all-features --test e2e_vendor_pypi_build -- --include-ignored(uv 0.x local): 36 passed, 0 failed.Checklist
vendor --revert/remove/rollbackdelete the vendored wheel while auv export-ed requirements.txt or pylock.toml still points at it (exit 0), so installs from the exported file fail #996:uv_revert_keeps_artifact_while_export_references_it,script_lock_revert_keeps_artifact_while_export_references_it,uv_vendor_revert_keeps_wheel_while_export_references_it(e2e)vendor --revert/removedelete the vendored wheel while a-rinclude still points at it (exit 0), so every laterpip install -r requirements.txtfails #867:requirements_revert_keeps_artifact_for_moved_vendor_line(both the include and the sibling variant)Follow-ups
vendor_artifact_kept/vendor_revert_kepttexts still say "lock entries drifted". The newvendor_revert_residual_referencewarning next to them gives the real reason. Rewording those shared messages is left out to keep this PR focused.requirements/dev.txtthat no-rline reaches) aren't probed.🤖 Generated with Claude Code
https://claude.ai/code/session_016w5DTe9ejKmm3mvdbi8VXE
Note
Medium Risk
Changes PyPI vendor revert and artifact deletion semantics across all flavors; incorrect probing could either leave broken installs or block cleanup, but behavior is heavily tested and fail-closed on unreadable files.
Overview
PyPI
vendor --revertno longer deletes the vendored wheel when another project file still installs from.socket/vendor/pypi/<uuid>/. After a wired flavor restores its recorded wiring, a shared residual-reference sweep runs before artifact removal. If anything still names the uuid path, revert stays successful but keeps the wheel and ledger entry and emitsvendor_revert_residual_referencenaming the blocking file (e.g. auv exportrequirements.txtorpylock.toml, or a vendor line moved into a-rinclude or sibling*.txt).The probe is generalized as
pypi_reference_clause: it now scans root-level*.txt, walks-rincludes via byte-awareprobe_include_names, and matches the needle across UTF-8 / UTF-16 viaprobe_textso unrelated non-UTF-8 files do not spuriously block cleanup.--dry-runpreviews the same warning while skipping files the flavor would restore. A second revert after re-export or fixing the file completes cleanup.Coverage is added with unit tests in
pypi.rsand a real-uv exporte2e ine2e_vendor_pypi_build.rs(two-step revert).Reviewed by Cursor Bugbot for commit cce2b28. Configure here.
Generated by Claude Code