Repository navigation
Fix crawl of --system-site-packages venvs (#409) - #1419
Mikola Lysenko (mikolalysenko) wants to merge 3 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A venv made with --system-site-packages imports packages from its base interpreter (Debian dist-packages, the base site-packages, the user site). pip treats a release found there as already installed and leaves it in place, but socket-patch only looked inside the venv. So vex attested an unpatched copy as not_affected from the lock pin, the stale-install warning never fired, and agent mode said "not installed". Local discovery now reads include-system-site-packages and home from each venv's pyvenv.cfg and appends the base interpreter's site dirs after the venv's own. It derives them from the filesystem only; the project's interpreter is never run. A base copy the venv also installs (by name) is hidden, since Python imports the venv's copy first. Refs #409 Assisted-by: Claude Code:claude-opus-5-5
A base package that two venvs share stays visible unless every venv installs its own copy, so a venv without one is still checked against the base copy it imports. A pyvenv.cfg with no include-system-site-packages key now counts as on, matching CPython. Free-threaded venvs (python3.13t) find their base site dirs. Refs #409 Assisted-by: Claude Code:claude-opus-5-5
|
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 f1ba00d. Configure here.
|
Ready for review at
Generated by Claude Code |
Final review briefWhat it does. A venv created with Risk: medium. The discovery and shadowing logic is self-contained and well tested. Two behavior changes are worth a deliberate yes: (1) agent-mode apply can now write into a base interpreter's site dir, outside the project venv. It fails with EACCES on a distro Python and succeeds on pyenv/Homebrew/user site. (2) Look here
Verified
Changes I made: none. Open questions
Auto-merge (squash) is armed, so approving sends this straight to the merge queue. Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #409
Summary
A venv made with
--system-site-packagesimports packages from its base interpreter (Debiandist-packages, the basesite-packages, the user site). pip treats a release found there as already installed and leaves it in place. socket-patch only looked inside the venv, so:vexthought nothing was installed and attestednot_affectedfrom the lock pin, even though the code that runs is not patched.This covers plain
python -m venv --system-site-packages/ virtualenv, Poetry'svirtualenvs.options.system-site-packages, and Pipenv's--site-packages/PIPENV_SITE_PACKAGES. All of them writeinclude-system-site-packages = trueintopyvenv.cfg.Root cause
Local Python discovery (
find_local_venv_site_packages,non_hatch_local_venv_site_packages, and through themPythonCrawler::get_site_packages_paths) returned only the venv's ownsite-packages. It never readpyvenv.cfg, so the base interpreter's site dirs behind a system-site venv were never crawled.Fix
Discovery (
add_system_site_packages): for each discovered venv whosepyvenv.cfgenables system site-packages, the base interpreter's site dirs are appended after the venv's own, insys.pathorder:PYTHONNOUSERSITEis set;/usr/local/lib/pythonX.Y/dist-packageswhen the prefix is/usr;<prefix>/lib{64,}/pythonX.Y/site-packages;<prefix>/lib/python3/dist-packages.The prefix comes from
home, following a symlinked interpreter to its stdlib landmark (Homebrew, Debian/usr/local/bin/python3 -> /usr/bin/python3.11). Only the venv's ownX.Y(including atfree-threaded suffix) is probed. Everything is derived frompyvenv.cfgand the filesystem: the project's interpreter is never executed, because it is untrusted project content.pyvenv.cfgis parsed like CPython'ssite.venv(): keys are case-insensitive, the last one wins, and a missing key counts astrue.Shadowing (
PythonCrawler::set_search_order): Python imports the first copy of a name onsys.path, and pip treats that copy as the installed one. So a base copy is hidden when the venv (or an earlier base dir) installs the same name. That case is common afterpip install --force-reinstallin the venv, which leaves the base copy in place. Without hiding it, the stale guard would keep warning,vexwould refuse forever, and agent apply would try to write into/usr/lib/python3/dist-packages. A base dir that several venvs share hides only what every one of them hides.get_site_packages_pathsdeclares its own order. The two call sites that use the free discovery functions, the hosted stale probe and the vendored Pipenv stale probe, declare it explicitly. Global mode clears it, so every copy stays visible there.Tests (red → green)
main)--vexover an unpatched base copy behind a system-site venvin_process_redirect_poetry::stale_base_copy_behind_system_site_venv_warns_and_cannot_attestredirect_pypi_stale_install, exit 1, no VEX filepython_crawler::tests::system_site_venv_adds_base_site_dirs_after_its_ownvexcopies)python_crawler::tests::base_copy_behind_a_system_site_venv_is_crawledin_process_redirect_poetry::venv_copy_shadows_stale_base_copy_behind_system_site_venv,python_crawler::tests::venv_copy_hides_the_base_copy_of_the_same_packagePYTHONNOUSERSITE; base-dir order; shared-base intersection;pyvenv.cfgparsing; symlinked prefix; Debian dirsisolated_venv_never_reaches_its_base_interpreter,system_site_venv_reaches_the_user_site_unless_disabled,earlier_base_site_dir_hides_a_later_one,shared_base_copy_stays_visible_to_a_venv_without_its_own,pyvenv_cfg_is_read_like_site_py,base_prefix_*,base_site_dirs_cover_debian_dist_packages_for_the_venv_version_onlyI also checked the derived dirs by hand against a real Debian
python3 -m venv --system-site-packages(Python 3.11):sys.path= venvsite-packages,/usr/local/lib/python3.11/dist-packages,/usr/lib/python3/dist-packages, which matches the discovery order.Local checks:
cargo fmt --all -- --check✅,cargo clippy --workspace --all-features -- -D warnings✅,cargo test --workspace --all-features✅ except tests that fail only because this sandbox runs as root, so permission-denied setups (chmod 0555, unremovable files) don't take effect. These are the*write_failure*/*unremovable*/*cleanup_failure*tests incovgap_commands_vendor,in_process_redirect,repairand the core lib (copy_tree::relax_loop_must_not_traverse_symlinked_root,vlt_heal::an_unremovable_hidden_lock_keeps_every_store_entry,pypi_poetry/pypi_requirementswire-failure tests). There is alsoe2e_bun_lockb::binary_shared_bundled_record_hosted_pin_is_managed, which fails the same way onorigin/mainhere. None of them involve Python discovery, and CI runs them green on this head. The Python e2e suite (e2e_pypi) needs the live API, so CI covers it.CI: green on
f1ba00d(36 pass / 50 path-skipped). Bugbot: no issues onf1ba00d.Decisions and follow-ups (for reviewers)
/code-review highfindings not fixed here:PythonCrawlerinstance, not in the discovery return type. That would be a cleaner API, but it would change every Python consumer's signature.get_site_packages_pathsresets the state, and each of the two free-function callers sets it.set_search_orderlists the venv and base dirs once more, only when a system-site venv exists.hatch_stale_install_warning) still judges only Hatch's own env dirs. A Hatch env withsystem-packages = truegets the base copy judged by the hosted guard (generic remedy), but not by the vendored Hatch-specific warning.🤖 Generated with Claude Code
https://claude.ai/code/session_01MPfWuTkaymyECuH3w5YJ49
Generated by Claude Code