Skip to content

Vendored scan of a fresh uv checkout (uv.lock, no .venv yet) exits 1 on packages that exist only in the system Python, because the crawler falls back to the global site-packages (the uv side of #947) #964

Description

[agent] Found by the scheduled uv bug-hunt routine (ledger #310).

Summary

This is the uv counterpart of #947 (Pipenv), and the open fix PR for #947 (#950) doesn't cover it. #950 only stops the fallback for Pipenv projects (is_pipenv_project → empty). It keeps crawler_python_e2e.rs::get_site_packages_paths_falls_back_via_uv_lock_marker, which pins the uv.lock → global fallback.

In a uv project checked out fresh (pyproject.toml + uv.lock, before uv sync, so there's no .venv), PythonCrawler::get_site_packages_paths finds no venv. is_python_project(cwd) is then true because of uv.lock / pyproject.toml, so the crawl falls back to get_global_python_site_packages(). Every package in the OS Python joins the candidate set (scannedPackages: 144 for a 2-package lock). scan --mode vendored then tries to vendor the system-only ones into uv.lock and fails:

pkg:pypi/pyyaml@6.0.1  failed  pypi_uv_lock_package_missing
  "uv.lock has no [[package]] entry for pyyaml; run `uv lock` first"
status: partial_failure, exit 1

The suggested remedy can't help, because pyyaml isn't a project dependency. --dry-run exits 0 with status: success and doesn't flag the refusal.

Impact

  • CI breakage in the documented vendored flow. A fresh checkout is exactly where vendored mode runs. GitHub ubuntu-* runners and Debian/Ubuntu images ship a system Python with PyYAML, requests, urllib3, jinja2, cryptography, six, idna and more. Whenever Socket has a patch for any of them, every vendored scan of every uv project on that machine exits 1. That includes projects whose own dependencies have no patches at all (repro below: nothing in the lock is patched, and the run still fails).
  • A system package that shares a name with a lock package masks the lock version. Here the system six 1.16.0 counts as "installed", so lockfileOnlyPackages is 1 instead of 2, and the system copy's bytes stand in for the project's.
  • Patch counts, the rollout budget (--max-new-patches) and the report include packages that aren't part of the project.
  • Nothing outside the project is written in vendored or hosted mode. The agent-mode write to the system Python is the Agent-mode scan in a Pipenv project without a Pipenv venv patches the system Python's site-packages in place instead of the project's venv/ (regression from #388) #504 shape, which the uv ledger tracks as a known generic fallback.

Repro (Linux, Debian/Ubuntu system Python with python3-yaml 6.0.1; a mock public proxy, --proxy-url, offers one free patch for pkg:pypi/pyyaml@6.0.1 and nothing else)

mkdir app && cd app
cat > pyproject.toml <<'EOF'
[project]
name = "app"
version = "0.1.0"
requires-python = ">=3.9"
dependencies = ["six==1.16.0", "idna==3.7"]
EOF
uv lock                      # lock only: a fresh checkout, no .venv
env -u VIRTUAL_ENV socket-patch scan --mode vendored --yes --ecosystems pypi --json \
    --proxy-url http://127.0.0.1:8767 > out.json; echo "exit=$?"
# exit=1, status partial_failure, scannedPackages 144, lockfileOnlyPackages 1
# vendor.events: pkg:pypi/pyyaml@6.0.1 failed pypi_uv_lock_package_missing
python3 -c "import yaml; print(yaml.__file__)"   # /usr/lib/python3/dist-packages/yaml/__init__.py

Control: run uv sync first, so .venv exists. The same scan crawls only the venv (scannedPackages: 2, lockfileOnlyPackages: 0), finds no patch, and exits 0. With a mock that also patches six, the run fails the same way for pyyaml while six goes down the normal vendoring path.

Expected vs actual

  • Expected: CLI_CONTRACT.md, "cwd-only (single project)", says the pypi crawler inspects only the project rooted at --cwd: the manager-recorded env (UV_PROJECT_ENVIRONMENT), $VIRTUAL_ENV, .venv / venv. Global packages are the -g / --global-prefix surface. Lock-only packages already come from uv.lock (lockfileOnlyPackages), so on a fresh checkout the lock is the whole candidate set. The run should vendor what the lock holds, exit 0, and never mention pyyaml.
  • Actual: system-Python packages join the candidate set. Vendored exits 1 with pypi_uv_lock_package_missing and a misleading uv lock remedy, --dry-run reports success, and system copies mask lock entries.

Cells (main 9c43dfc, CLI 4.0.0, Linux x86_64, system Python 3.11/3.12 dist-packages)

uv (writes uv.lock) lock revision vendored, no .venv (×2) --dry-run hosted after uv sync (control)
0.5.31 v1 ❌ exit 1, pypi_uv_lock_package_missing (pyyaml) exit 0, success 144 scanned, pyyaml listed as a project package —
0.8.17 v1 rev 3 ❌ exit 1 (same) exit 0, success same —
0.12.23 v1 rev 5 ❌ exit 1 (same) exit 0, success same ✅ exit 0, 2 scanned

The behaviour doesn't depend on the uv version: uv only writes the lock, and the crawl is the same for every lock grammar. macOS and Windows weren't probed (the fallback is cross-platform: get_global_python_site_packages also scans Homebrew, python.org and conda roots). Not bisected; the fallback predates vendored uv support.

Suspect code

  • crates/socket-patch-core/src/crawlers/python_crawler.rs:3047: if is_python_project(&options.cwd).await { return Ok(get_global_python_site_packages().await); } in non-global mode. The doc comment above it ("a fresh clone with pyproject.toml + uv.lock but no .venv would silently return zero packages") predates lock-only discovery. That case is now covered by the lock merge.
  • crates/socket-patch-core/tests/crawler_python_e2e.rs:1124: get_site_packages_paths_falls_back_via_uv_lock_marker pins the fallback for uv.lock. PR Fix Pipenv project falling back to system Python (#504, #947) #950 leaves it in place.
  • crates/socket-patch-core/src/vendor/pypi_uv.rs:330-334: the pypi_uv_lock_package_missing detail recommends uv lock, even when the package isn't a project dependency.

Related: #947 / PR #950 (the Pipenv half; this report came from the Pipenv routine's handover), #504 (agent-mode writes), #476 (Poetry), #502 (PDM).

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions