Repository navigation
Fix PyPI vendored wheels lost to gitignore/LFS (#1572) - #1593
Queued
Mikola Lysenko (mikolalysenko) wants to merge 5 commits into
Queued
Mikola Lysenko (mikolalysenko) wants to merge 5 commits into
Mikola Lysenko (mikolalysenko) wants to merge 5 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Vendoring a PyPI package wrote the patched wheel into .socket/vendor/pypi/<uuid>/ without asking git whether the commit would keep it. A repo that ignores *.whl or .socket/, or tracks *.whl with Git LFS, committed a lock pointing at a wheel that every fresh clone was missing (or got as an LFS pointer), so `pipenv sync`, `pip install -r`, `uv sync` and friends failed there, while vendoring had exited 0. The uuid dir now gets the same protection npm, NuGet and the JVM trees have: a .gitignore (`!*`) that re-includes the wheel against file rules such as *.whl, and a .gitattributes that unsets LFS and EOL filters. A rule that ignores the uuid dir itself (.socket/, .socket/vendor/) can't be overridden from inside it, so vendoring refuses with vendor_artifact_gitignored before downloading or writing anything, dry run included, and the hosted-to-vendored takeover refuses before it restores the hosted pin. Fixes #1572 Assisted-by: Claude Code:claude-opus-5-5
A hosted-to-vendored scan in a repo that ignores .socket/ now refuses the vendoring, and the takeover keeps the hosted pin, so the package stays patched instead of ending up neither hosted nor vendored. The takeover already rolls its restore back when the backend refuses, so the separate CLI pre-flight from the previous commit is dropped. Refs #1572 Assisted-by: Claude Code:claude-opus-5-5
A PyPI wheel vendored before the previous fix has no uuid .gitignore or .gitattributes, and the in-sync re-run returned before writing them, so existing projects kept losing the wheel to *.whl or LFS rules. The in-sync re-run now adds them, and `repair` writes them when it restores a deleted uuid dir. The .socket/ refusal now runs only when the run is about to write the uuid dir, so an in-sync re-run that writes nothing new (for example over a wheel force-added past a .socket/ rule) is no longer refused. The PyPI dotfiles share npm's metadata writer, and the contract's vendor_artifact_gitignore_unchecked row now names PyPI. Refs #1572 Assisted-by: Claude Code:claude-opus-5-5
Mikola Lysenko (mikolalysenko)
marked this pull request as ready for review
October 15, 2026 23:21
Collaborator
Author
|
BugBot review Generated by Claude Code |
When a re-run rebuilt a missing wheel that the lock already names, and writing the uuid .gitignore/.gitattributes (or the ignore probe) then failed, the run swept the rebuilt wheel away again, leaving installs pointing at a missing file. The wheel is now kept; only a fresh wheel nothing names yet is swept. The commit check also moves after the in-sync pin check, so a rebuilt wheel that doesn't match the pin is always swept first. Refs #1572 Assisted-by: Claude Code:claude-opus-5-5
Collaborator
Author
|
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 85a0d5e. Configure here.
Collaborator
Author
|
[agent] Ready for review at
Generated by Claude Code |
Collaborator
Author
|
Labeled Ready for review at head
Generated by Claude Code |
Wenxin Jiang (Wenxin-Jiang)
approved these changes
Oct 19, 2026
Mikola Lysenko (mikolalysenko)
added this pull request to the merge queue
Oct 20, 2026
Any commits made after this event will not be merged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1572
Summary
Vendored PyPI wheels now get the same commit protection npm, bun, NuGet and the JVM trees already have. Before this, a repo that ignored
*.whlor.socket/, or tracked*.whlwith Git LFS, would commit a lock pointing at a wheel that every fresh clone was missing (or got as an LFS pointer).pipenv sync,pip install -r,uv syncand the rest then failed on that clone, even though vendoring had exited 0.Root cause
All PyPI flavors (requirements.txt, uv, Poetry, PDM, Pipenv, Hatch, pylock) go through
vendor_pypi_with_pipenv_versioninvendor/pypi.rs. That function wrote the wheel into.socket/vendor/pypi/<uuid>/without agit check-ignoreprobe and without an owned<uuid>/.gitignore/.gitattributes. Because the gap is in that shared path, one fix covers every flavor.Changes
keep_wheel_committable): once the wheel is in place (fresh download, reused committed wheel, or in-sync rebuild), write<uuid>/.gitignore(!*) and<uuid>/.gitattributes(* -text -filter -ident -working-tree-encoding). Then probe the wheel and both files withgit check-ignore. If they're still ignored, the run refusesvendor_artifact_gitignoredand sweeps a uuid dir it created. If git can't answer, the run warnsvendor_artifact_gitignore_unchecked..socket/or.socket/vendor/rule can't be overridden from inside the uuid dir. So when a run is about to write the dir, it refusesvendor_artifact_gitignoredbefore downloading or writing anything, dry run included. The check sits inpypi_prelude, so the download plan (service_preflight) gives the same answer. A hosted→vendored takeover that hits this keeps the hosted pin, becauseTakeoverUndoalready rolls the restore back.repairwrites them again when it restores a deleted uuid dir. An in-sync re-run that writes nothing new is not refused by a.socket/rule.npm_dir::write_uuid_metadata).CLI_CONTRACT.mdnow lists PyPI in thevendor_artifact_gitignoredandvendor_artifact_gitignore_uncheckedrows.Test evidence
*.whlrulevendor::pypi::tests::a_whl_ignore_rule_is_overridden_by_the_uuid_gitignorevendor::pypi::tests::the_uuid_gitattributes_unsets_a_repository_lfs_rule_for_wheels.socket/rule (.socket/,.socket/vendor/,pypi/; wet + dry run)vendor::pypi::tests::a_directory_ignore_rule_refuses_before_any_writevendor::pypi::tests::an_in_sync_rerun_restores_the_uuid_dotfilesvendor::pypi::tests::an_in_sync_rebuild_keeps_its_wheel_when_the_dotfiles_failin_process_vendor_pypi_takeover::scan_vendored_over_hosted_with_socket_dir_ignored_keeps_the_hosted_pinLocal runs:
cargo fmt --all -- --check: clean.cargo clippy --workspace --all-features -- -D warnings: clean.cargo test --workspace --all-features --no-fail-fast: everything passes except 9 tests, and all 9 are artifacts of this sandbox, not of this change:pypi_poetry::wire_write_failure…,pypi_requirements::wire_failure_rolls_back…) pass when re-run asnobody.patches-api.socket.dev, which the sandbox blocks.e2e_vendor_pypi_build --include-ignored(uv 0.11.32): 55/55.mode_migration_pypi: 64/64.e2e_pypineeds the live patch API, which isn't reachable from the sandbox, so it's left to CI.Review notes
Bugbot (43f8e3a): an in-sync rebuild was swept when the commit check failed. Fixed in 85a0d5e, which also moves the check after the in-sync pin check.
/code-review high:Fixed: in-sync re-runs skipped the dotfiles;
repairdidn't restore them; the root refusal blocked in-sync runs over force-added wheels; the contract row forgitignore_unchecked; a weak assertion; duplicated metadata writer; extra git spawns on in-sync runs (the probe now runs only when the run writes).Not changed:
🤖 Generated with Claude Code
https://claude.ai/code/session_01MccSpwBKRT9e2VHyxDyqzH
Note
Medium Risk
Changes shared PyPI vendor/repair paths and git commit semantics; failures are explicit refusals rather than silent broken clones.
Overview
PyPI vendoring now matches npm-family ecosystems: vendored wheels under
.socket/vendor/pypi/<uuid>/get a uuid-level.gitignore(!*) and.gitattributes(to defeat*.whlignore rules and Git LFS), thengit check-ignoreruns so commits cannot silently omit the wheel.Refusal and rollback: If the uuid directory itself is ignored (e.g.
.socket/), the run fails withvendor_artifact_gitignoredbefore download or write (including dry run). If the wheel still looks ignored after the dotfiles, the new uuid dir is removed (except reused committed wheels). Hosted→vendored takeover keeps the hosted pin when refusal happens (#1572).Healing: In-sync re-runs add missing dotfiles on wheels vendored before this fix;
repairrestores them when rebuilding a deleted uuid dir.npm_dir::restore_uuid_metadatais split intowrite_uuid_metadataso PyPI can supply its own ignore text.CLI_CONTRACT.mddocuments PyPI undervendor_artifact_gitignored/vendor_artifact_gitignore_unchecked. Tests cover*.whl, LFS, directory rules, in-sync repair, and takeover behavior.Reviewed by Cursor Bugbot for commit 43f8e3a. Configure here.
Generated by Claude Code