Skip to content

Fix PyPI revert deleting still-referenced wheel (#996, #867) - #997

Open
Mikola Lysenko (mikolalysenko) wants to merge 10 commits into
mainfrom
agent/fix-pypi-revert-residual-probe
Open

Mikola Lysenko (mikolalysenko) wants to merge 10 commits into
mainfrom
agent/fix-pypi-revert-residual-probe

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #996
Fixes #867

Summary

A vendored Python vendor --revert, remove or rollback restored 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 reported success and exit 0.

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 root requirements.txt plus its -r includes, uv.lock, pylock*.toml, *.py.lock with their scripts, and pyproject.toml, hatch.toml, Pipfile*, poetry.lock and pdm.lock. But it only ran for ledger entries with no wiring. Every wired flavor went straight to deletion:

  • uv, script/pylock, Hatch, Poetry, PDM and Pipenv had no sweep at all.
  • The requirements flavor swept only the files it had recorded.

Fix

  • After every successful wired PyPI revert, and before the artifact is deleted, the same probe now runs (renamed pypi_reference_clause). It now also reads every root-level *.txt, which covers a uv export -o target and Vendored requirements.txt: vendor --revert / remove delete the vendored wheel while a -r include still points at it (exit 0), so every later pip install -r requirements.txt fails #867's non-included sibling.
  • If any file still names the uuid dir, the revert keeps the wheel and the ledger entry (kept_artifact, so the CLI reports vendor_revert_kept). It adds vendor_revert_residual_reference naming the file and the way out: point it back at the registry release or re-export it, then re-run vendor --revert.
  • The recorded wiring is still restored, and re-running the revert after fixing the file finishes the cleanup.
  • --dry-run previews the same warning. It skips the files the flavor would restore, and per the existing kept_artifact contract the keep flag itself stays wet-only.
  • The fix lives in the shared dispatcher, so all seven PyPI flavors are covered. The npm, PyPI and gem wrappers don't change: this is Rust-only revert logic.
  • A side effect: a hosted takeover's dry run now sees the residual warning through 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_helpers fails on main and turns test red on every PR. The commit no-ops once #878 lands.

Test evidence

New tests (red on main at 3ac5183, green with the fix at 66d2396):

Issue Test Without the fix
#996 uv export (requirements.txt + pylock.toml, dry run + wet + finish after re-export) vendor::pypi::tests::uv_revert_keeps_artifact_while_export_references_it FAILED: dry run had no residual warning ([])
#996 script lane (uv export --script) vendor::pypi::tests::script_lock_revert_keeps_artifact_while_export_references_it FAILED: no residual warning: []
#867 -r include and non-included sibling vendor::pypi::tests::requirements_revert_keeps_artifact_for_moved_vendor_line FAILED: only vendor_revert_line_drifted, wheel deleted
#996 real uv e2e e2e_vendor_pypi_build::uv_vendor_revert_keeps_wheel_while_export_references_it (real uv export, two-step revert) (new suite test)

Commands run locally:

  • cargo clippy --workspace --all-features -- -D warnings: clean.
  • rustfmt --check on the touched files: clean. CI doesn't run cargo fmt, and main isn'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 with chmod 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

Follow-ups

  • The vendor_artifact_kept / vendor_revert_kept texts still say "lock entries drifted". The new vendor_revert_residual_reference warning next to them gives the real reason. Rewording those shared messages is left out to keep this PR focused.
  • Files nested below the root that nothing includes (e.g. requirements/dev.txt that no -r line reaches) aren't probed.

🤖 Generated with Claude Code

https://claude.ai/code/session_016w5DTe9ejKmm3mvdbi8VXE


Note

Medium Risk
Changes shared PyPI revert deletion logic for all flavors; incorrect probing could leave stale vendor dirs or delete wheels still in use, though behavior is fail-closed with explicit warnings.

Overview
PyPI vendor --revert no longer deletes the vendored wheel when another project file still installs from .socket/vendor/pypi/<uuid>/. After wiring is restored, a shared probe scans locks, requirements.txt and -r includes, and root-level *.txt (e.g. uv export -o requirements.txt or a sibling like requirements-dev.txt). If anything still references the uuid path, revert succeeds but keeps the wheel and ledger and emits vendor_revert_residual_reference naming the blocking file; a second revert after re-export or fixing that file completes cleanup.

The former unwired-only probe is generalized as pypi_reference_clause (with optional skip for dry-run previews). --dry-run surfaces the same warning without setting kept_artifact. Coverage spans all wired PyPI flavors via revert_pypi_opts.

New unit tests (#996 uv/pylock/script export, #867 moved requirements lines) and an e2e test with real uv export and a two-step revert.

Reviewed by Cursor Bugbot for commit a4026cc. Configure here.


Generated by Claude Code

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
@mikolalysenko
Mikola Lysenko (mikolalysenko) force-pushed the agent/fix-pypi-revert-residual-probe branch from 82a409e to 66d2396 Compare October 7, 2026 09:37
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)
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 7, 2026 10:12
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] CI note: gradle 9.8.0 / jdk 21 / agent / windows-latest failed once on 50c18cd in gradle_agent_verification_metadata_refuses, before any socket-patch step ran. Gradle itself aborted with DaemonConnectionException: Could not dispatch a message to the daemon (An established connection was aborted by the software in your host machine). This PR changes only the PyPI revert path plus the ported Gradle digest-helper refactor, which can't affect a daemon socket. I re-ran the failed job once and it passed. All 541 checks are now green on 50c18cd, and Bugbot found no issues. The PR is waiting on human review.


Generated by Claude Code

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 7, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Ready for review at 50c18cd.

  • CI: 541/541 checks on 50c18cd pass (534 success, 6 skipped, 1 neutral).
  • Bugbot: reviewed 50c18cd, no findings. No open review threads.
  • Mergeability: 50c18cd merges cleanly into today's main (8cf1910), so the 28 PRs merged this morning didn't make it conflict.
  • Reviewer note: every wired PyPI revert (uv, script/pylock, Hatch, Poetry, PDM, Pipenv, requirements) now runs the reference probe before it deletes .socket/vendor/pypi/<uuid>/. A wheel that another project file still installs is kept and reported, not deleted.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] CI note on a4026cc: CodeQL Analyze (actions), Analyze (python) and Analyze (javascript-typescript) show as failed, but they never ran. Each job reports "The job was not started because it repeatedly failed to be acquired (5 attempts)", meaning GitHub couldn't get a runner for it. This isn't caused by this PR, and those three checks pass on main (431b818). I tried re-running the failed jobs once (run 37652921593), but the API refused with 403 This workflow run cannot be retried, so a maintainer needs to re-run CodeQL from the Actions UI or push a new commit. The rest of CI on a4026cc is still running.


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 a4026cc. Configure here.

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Ready for review at 2304041.

  • CI: 504/504 checks on 2304041 pass (498 success, 6 skipped, 0 failing), including test on all three OSes, test-release, coverage, clippy, CodeQL, and the e2e_vendor_pypi_build legs for every uv version.
  • Conflicts: none. I merged main twice: once before Fix main CI red on stale digest pending-list entries #1016 landed (ca49e25) and once right after it (8208c05). Both merges were clean. "Update branch" then brought in more of main (2bd6945, a4026cc, 2304041). Those merges were also clean, and the PR's diff against main is still just vendor/pypi.rs and e2e_vendor_pypi_build.rs.
  • Fix main CI red on stale digest pending-list entries #1016 interaction: after Fix main CI red on stale digest pending-list entries #1016 merged, production_digests_go_through_the_helpers passes on the merged tree. The cherry-picked Gradle digest commit (50c18cd) no longer adds anything, because main already has that change. The core lib suite passes locally (5636/5636) on a4026cc, which includes Fix remove/rollback missing PyPI name spellings (#1024) #1025's PyPI name-spelling change, and uv_vendor_revert_* e2e passes locally with uv.
  • Fixes: none needed.
  • CI flakes: the CodeQL actions, python and javascript-typescript jobs on a4026cc failed because GitHub couldn't get a runner for them. GitHub wouldn't let anyone re-run them. They pass on 2304041.
  • Bugbot: reviewed a4026cc and found no new issues. The only change since then is a main merge touching CI workflows (Run CI on the merge queue and stop cancelling main push runs #1018). There are no open review threads.

Generated by Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

2 participants