Repository navigation
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 --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. - The patch is silently lost: a plain
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. - 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 sixControl: 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 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.
Activity
mikolalysenko commentedon Oct 2, 2026
[agent] Triage: priority:p1 (uv). Confirmed on main: neither the hosted nor the vendored uv
[tool.uv.sources]writer checksno-sources(no occurrence ofno-sourcesinsocket-patch-core). Distinct from open #545 (sources-table spelling); no open PR covers it.Generated by Claude Code
mikolalysenko commentedon Oct 5, 2026
[agent] Re-triage from the uv bug-hunt routine (ledger #310): this still reproduces on main
99f61d2on Linux, against the local mock.Shape:
dependencies = ["six==1.16.0", "attrs>=20"]plus[tool.uv] no-sources = true, thenuv lock,uv sync,scan --mode <mode> --yes,uv sync --locked, then a plainuv syncand a check of whether the installed six carries the patch marker.redirect_pypi_stale_install),--lockedexit 2, plain sync installs unpatched six--lockedexit 2, plain sync unpatched--lockedexit 1--lockedexit 1 ("The lockfile atuv.lockneeds to be updated")Generated by Claude Code
uv: run 20 ledger (#869 filed; #411/#564 re-checked)
mikolalysenko commentedon Oct 9, 2026
v5 triage: P2, not a release blocker. Retain the explicit uv no-sources configuration conflict at P2; it is a supported-settings follow-up, not a demand for new recovery machinery.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
mikolalysenko commentedon Oct 12, 2026
[agent] Claiming this issue (shared root cause: the uv
[tool.uv.sources]writers never read uv'sno-sourcessetting, so they wire an entry uv ignores). Branch: agent/fix-uv-no-sources. Claim-ID: 2026-10-12T15:21:33Z-eeb571Generated by Claude Code
mikolalysenko commentedon Oct 12, 2026
[agent] Draft PR: #1434.
Generated by Claude Code
Refuse uv wiring that no-sources would ignore
Test and document the uv no-sources refusal
Follow uv's config lookup for scripts' no-sources
Refuse uv wiring that no-sources-package would ignore
Document the uv script parent config lookup