Fix vendored Python reverts refusing sibling edits (#385, #474) - #481
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Rolling back a vendored Hatch project failed with "pyproject.toml changed since patching" after a routine release bump, a comment on `name`, or a new dependency next to the patched one (#385). A vendored PEP 723 script lock stayed vendored for good once `uv add --script` added an unrelated requirement (#474). Both reverts go through one three-way TOML merge. It paired array elements by position, called any length change drift, and checked lock-package name/version on every table, including [project]. The merge now restores only the elements vendoring changed, finding each one in the live array by its text or its identity (PEP 508 name, or table name + version). Siblings the user added, dropped or moved are left alone. A vendored element that was edited, re-resolved, removed or duplicated is still reported as drift. Assisted-by: Claude Code:claude-opus-5-5
Adds end-to-end steps with the real tools: the vendored uv script lane now runs `uv add --script tool.py idna==3.7` on a copy before `vendor --revert` (#474), and the vendored Hatch project lane bumps the release and adds a dependency before reverting (#385). Both check the user's edits survive and the vendored wiring is gone. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
Review found a gap in the new array merge: if the user renamed the vendored element past recognition and also added a sibling equal to the original pin, the revert paired the sibling as "already restored", reported success, and deleted the vendored artifact the renamed line still pointed at. An "already restored" pairing now only counts when every other live element is accounted for. Otherwise the revert reports drift and keeps the artifact. Elements already back to their original text are also skipped regardless of their spacing. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
[agent] Ready for review on Generated by Claude Code |
|
Reviewed P1 — Added sibling requirements can retain a reference to a wheel that revert deletes ( Reproduced through the CLI: vendor a Hatch Validation: 21 |
Review found that a user-added copy of the vendored requirement (for example with a `python_version` marker) survives the array merge as a new sibling. The revert then reported success and deleted the wheel that line still installs from. Before a Hatch or Python-lock revert counts as complete, it now checks that the restored file no longer mentions this patch's `.socket/vendor/pypi/<uuid>` directory. If it does, Hatch fails and the lock revert reports `vendor_lock_entry_drifted`, so the file and the artifact are kept. Adds unit tests and a real-Hatch e2e step that fail without the check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPpPuqqqgLkPXkcpsFfKFo
|
[agent] Re the P1 in your review: confirmed and fixed in d1698d3, which also merges Before a Hatch or Python-lock revert counts as complete, it now checks whether the restored file still mentions this patch's Tests (each fails without the check):
Re-ran locally: vendor unit tests (2,132 pass; the 2 that fail are root-only chmod tests that also fail on Generated by Claude Code |
|
BugBot review Generated by Claude Code |
Bugbot found that when the user removes the vendored array row, the merge could still pair it by name with another row (a requirement's identity ignores `specifier`). It then filled the original fields into that row and reported a clean revert, rewriting the user's entry. A table paired only by identity must now still hold at least one field exactly as the vendoring wrote it, such as the vendored `path` or `source`. Otherwise the revert reports drift and keeps the file and the artifact. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPpPuqqqgLkPXkcpsFfKFo
|
BugBot review Generated by Claude Code |
|
Follow-up review of Validation: 23 Python-lock tests passed at this exact head, including the new namesake-row regression and leftover-artifact check. The original independent Hatch CLI reproduction passed on the preceding commit No remaining blocking finding in the updated code. This supersedes my earlier changes-needed recommendation. |
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 f3f20c6. Configure here.
|
[agent] CI:
No fix to port. GitHub won't re-run the job while the rest of its workflow run is still going, so I'll re-run it once when the run finishes. If it fails again, I'll treat it as a real failure and investigate it. Generated by Claude Code |
|
[agent] The re-run of Generated by Claude Code |
|
Second pass reviewed Prior validation remains applicable: 23 Python-lock tests passed at this exact head; the independent Hatch CLI reproduction passed at |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #385
Fixes #474
Summary
Rolling back a vendored Python project no longer fails just because the user made an everyday edit next to the patched pin:
rollback/remove/vendor --revertused to fail withpyproject.toml changed since patchingafter a[project].versionbump, a comment onname, or a new entry inproject.dependencies. They now restore six's pin, drop theallow-direct-referencespermission, and keep the user's edits.uv add --scriptadds an unrelated dependency (vendor_lock_entry_drifted, still exit 0) #474): afteruv add --script tool.py <pkg>,vendor --revertused to reportvendor_lock_entry_driftedand keep the vendored wiring for good (still exiting 0). It now restores six's[manifest] requirementselement and[[package]]source and keeps the added requirement. uv accepts the result as is (uv lock --scriptleaves it unchanged).Root cause
Both reverts go through one three-way TOML merge,
vendor::pypi_lock::restore_document(also used by the hosted Hatch helper inutils/hatch.rs). It:name/versionidentity check on every table, including pyproject[project], so a release bump counted as drift.Fix
original[i] != new[i]) are looked up in the live array: first by exact text (ignoring the element's own whitespace/comments), then by identity (PEP 508 project name for requirement strings, canonicalname+versionfor tables). Each one must map to exactly one live element, otherwise it is drift. Only those elements are restored, and the user's other elements are left as they are. A restored inline element keeps the live spacing/comments unless they are still the vendored ones.[project].Tests (red → green)
vendor::pypi_hatch::tests::revert_keeps_unrelated_project_edits(version bump, comment onname, added dependency, added key)e2e_vex_build hatch::hatch_project_dependency_vendorednew steprevert-after-project-edits(real Hatch 1.18.1: bump +idna==3.7, thenvendor --revert)revert after project edits: exit Some(1)vendor::pypi_lock::tests::added_sibling_requirement_does_not_block_script_lock_reverte2e_vendor_pypi_build vendored_uv_script_lock_manifestless_vexnew stepsibling-revert(real uv 0.8.17:uv add --script tool.py idna==3.7,vendor --revert,uv lock --scriptleaves the lock unchanged)an added sibling counted as driftrestored, user addition keptsibling_strings_added_or_removed_around_the_vendored_element,re_resolved_package_with_added_sibling_is_drift,vendored_element_edited_or_removed_is_still_driftBugbot follow-up (1e6967d): a vendored element renamed past recognition plus a user-added twin of the original pin is drift, not "already restored" (
already_restored_element_is_trusted_only_without_unknown_siblings, and a new case invendored_element_edited_or_removed_is_still_drift; both red without the guard).Existing drift tests (
conflicting_fields_are_preserved_and_reported,reordered_packages_cannot_receive_another_packages_original, out-of-order and CRLF reverts, Hatch shared-permission rollback) still pass unchanged.Commands run locally:
cargo clippy --workspace --all-features -- -D warnings: clean.cargo test --workspace --all-features --no-fail-fast: everything passes except 12 tests that make a directory read-only withchmodto simulate write failures. This sandbox runs as root, which ignoreschmod, so they fail identically onmain(re-ran them against main's code). None of them involve the TOML merge.SOCKET_PATCH_UV_E2E_REQUIRED=1 cargo test -p socket-patch-cli --all-features --test e2e_vendor_pypi_build -- --include-ignored: 20/20 ok (uv 0.8.17).SOCKET_PATCH_HATCH_E2E_REQUIRED=1 SOCKET_PATCH_HATCH_E2E_VERSION=1.18.1 cargo test -p socket-patch-cli --all-features --test e2e_vex_build -- hatch:: --ignored: 4/4 ok.cargo fmt --all -- --checkis already red onmain(CI doesn't run it). The changed files are formatted with the pinned 1.93.1 rustfmt, and the diff only touches the changed hunks.No wrapper (
npm/,pypi/,gem/) changes are needed: the merge is core-only.Not in scope
#473 (hosted uv
include-grouprestore inredirect/upstream/uv.rs), #379 and #382 are different code paths.🤖 Generated with Claude Code
Note
Medium Risk
Changes core three-way TOML merge and revert success criteria for Python vendoring; incorrect pairing could restore wrong pins or refuse valid reverts, though ambiguous cases are designed to fail closed.
Overview
Vendored Python rollback no longer treats unrelated
pyproject/lock edits as drift:restore_documentinpypi_lock.rsnow pairs only vendoring-changed array entries (by text or PEP 508 /name+versionidentity) instead of by index/length, so siblings like version bumps, new dependencies, oruv add --scriptadditions are kept while the patched pin is restored.Fail-closed guard:
still_references_artifactblocks a “successful” revert when a user-added copy of the vendored requirement still points at.socket/vendor/pypi/{uuid}—wired into Hatch revert and lock revert warnings—so wheels are not dropped while still referenced.Tests: unit cases for siblings, namesakes, and leftover artifact refs; Hatch e2e (
revert-after-project-edits,revert-refuses-copied-reference); uv script e2escript_sibling_revert(#474).Reviewed by Cursor Bugbot for commit f3f20c6. Configure here.
Generated by Claude Code