[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.
[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 repointuv.lockto match. When the project setsno-sources = true(in[tool.uv]ofpyproject.tomlor in a projectuv.toml), uv ignores[tool.uv.sources]entirely. Neither writer checks for this. Both reportsuccess/ exit 0 and leave a lock that uv considers outdated.Impact
uv lock --checkanduv sync --lockedfail ("The lockfile atuv.lockneeds to be updated, but--lockedwas provided"). That's exit 1 on uv 0.8.17 / 0.12.22 and exit 2 on 0.5.31.uv sync(oruv lock) relocks six toregistry = "https://pypi.org/simple". On a fresh checkout it installs the unpatched upstream wheel. Onlyuv sync --frozeninstalls the patch.Repro (Linux, main
61cfb9b, real uv 0.8.17)Control: the same project without
no-sources = truepassesuv lock --checkanduv sync --locked, and a plainuv synckeeps the patch (hosted and vendored, same run). Puttingno-sources = truein a projectuv.tomlinstead gives the same failure (vendored, 0.8.17).Expected vs actual
[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.--locked, and an ordinary install drops the patch.OS × version (Linux; the check is config-driven, so it doesn't depend on the OS)
[tool.uv] no-sources = true[tool.uv] no-sources = trueuv.tomlno-sources = trueno-sources), hosted and vendoredNot bisected.
git grep no-sourceson 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 readstool.uv.no-sourcesoruv.toml.crates/socket-patch-core/src/utils/python_script.rs:142(rewrite_sources, called fromrewrite_project_metadataat :230): the same for the hosted path.