Skip to content

Hosted and vendored uv wiring ignore no-sources = true: scan and vendor report success, uv sync --locked then fails, and a plain uv sync reinstalls the unpatched wheel #564

Description

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

Summary

Hosted (scan --mode hosted) and vendored (vendor) wiring for uv projects both route the patched package through a [tool.uv.sources] entry, and they repoint uv.lock to match. When the project sets no-sources = true (in [tool.uv] of pyproject.toml or in a project uv.toml), uv ignores [tool.uv.sources] entirely. Neither writer checks for this. Both report success / exit 0 and leave a lock that uv considers outdated.

Impact

  • Locked CI breaks right after wiring: uv lock --check and uv sync --locked fail ("The lockfile at uv.lock needs to be updated, but --locked was provided"). That's exit 1 on uv 0.8.17 / 0.12.22 and exit 2 on 0.5.31.
  • The patch is silently lost: a plain uv sync (or uv lock) relocks six to registry = "https://pypi.org/simple". On a fresh checkout it installs the unpatched upstream wheel. Only uv sync --frozen installs the patch.
  • No warning or refusal is emitted, so the user believes the project is patched.

Repro (Linux, main 61cfb9b, real uv 0.8.17)

cat > pyproject.toml <<'EOF'
[project]
name = "ns"
version = "0.1.0"
requires-python = ">=3.9"
dependencies = ["six==1.16.0", "idna==3.7"]

[tool.uv]
no-sources = true
EOF
uv lock && uv sync --no-install-project
# a free six 1.16.0 patch: .socket/manifest.json + blob for vendor, or the hosted routes (local mock patch API)
socket-patch vendor --json            # status success, applied 1; writes [tool.uv.sources] six = { path = ".socket/vendor/pypi/<uuid>/six-1.16.0-py2.py3-none-any.whl" }
#   or: socket-patch scan --mode hosted --json --yes --api-url $API ...   # status success, redirected 1; writes six = { url = "<hosted wheel>" }
uv lock --check                        # exit 1: lock needs to be updated
rm -rf .venv && uv sync --locked --no-install-project   # exit 1
rm -rf .venv && uv sync --frozen --no-install-project   # installs the patched wheel
rm -rf .venv && uv sync --no-install-project            # relocks six to the PyPI registry; installs UNPATCHED six

Control: the same project without no-sources = true passes uv lock --check and uv sync --locked, and a plain uv sync keeps the patch (hosted and vendored, same run). Putting no-sources = true in a project uv.toml instead gives the same failure (vendored, 0.8.17).

Expected vs actual

  • Expected: docs/testing/uv-compatibility.md says the [tool.uv.sources] entry "carr[ies] the redirect (verified with --frozen, --locked, and ordinary installs)". When a project setting stops uv from honouring that entry, the writers should refuse or at least warn before writing anything, the way they already refuse other configurations they can't wire (redirect_uv_project_unsupported, pypi_uv_source_already_exists). That would leave the project untouched.
  • Actual: both modes write the source plus the lock repoint and report success. The result fails --locked, and an ordinary install drops the patch.

OS × version (Linux; the check is config-driven, so it doesn't depend on the OS)

Mode uv 0.5.31 uv 0.8.17 uv 0.12.22
vendored, [tool.uv] no-sources = true ❌ ❌ (reproduced 2×) ❌
hosted, [tool.uv] no-sources = true ❌ ❌ (reproduced 2×) ❌
vendored, uv.toml no-sources = true – ❌ –
control (no no-sources), hosted and vendored ✅ ✅ ✅

Not bisected. git grep no-sources on main finds nothing in the crates or docs.

Suspect code

  • crates/socket-patch-core/src/vendor/pypi_uv.rs:589: ensure_table(&mut doc, &["tool", "uv", "sources"]) is written unconditionally. Nothing reads tool.uv.no-sources or uv.toml.
  • crates/socket-patch-core/src/utils/python_script.rs:142 (rewrite_sources, called from rewrite_project_metadata at :230): the same for the hosted path.

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

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions