Repository navigation
uv PEP 723 script in a subdirectory: hosted and vendored wiring ignore no-sources = true from a parent uv.toml / pyproject.toml, so uv run --locked fails and a plain uv run imports the unpatched package #1501
Description
[agent] Found by the scheduled uv bug-hunt routine (ledger #310).
Summary
#1434 (the #564 fix) makes both modes refuse a uv project or PEP 723 script under no-sources = true / no-sources-package. It reads the settings only from the files beside the scanned root: the script itself, <root>/pyproject.toml and <root>/uv.toml. For a script, uv reads its settings from the nearest ancestor directory that holds a uv.toml or a pyproject.toml with [tool.uv]. A common layout is a uv project whose root pyproject.toml sets [tool.uv] no-sources = true, with standalone PEP 723 scripts under scripts/. When socket-patch scans scripts/ (--cwd scripts, or scan scripts from the root), it finds no setting and wires the script. uv then ignores the source it wrote:
scan --mode vendored/--mode hostedexit 0 (hosted reports no warning at all) and rewrites.py+s.py.lock.uv lock --script s.py --checkanduv run --locked --script s.pyfail ("The lockfile … needs to be updated").- A plain
uv run --script s.pyre-resolves from PyPI and imports the unpatched module. On uv 0.5.31, evenuv run --lockedsilently runs unpatched.
The project lane is not affected. A pyproject.toml project in the subdirectory stops uv's config lookup at the project, and uv ignores the parent setting there, as socket-patch assumes.
Impact
This is the same failure #564 described, in a layout #1434 doesn't cover: a successful scan (and a vendored commit) that leaves the script unpatched or broken under --locked. A clean exit 0 hides it, and hosted mode doesn't print any advisory.
Repro (Linux, uv 0.13.0; the patch API is the local mock used on this ledger's probe branches, serving a patched six==1.16.0 wheel)
mkdir -p repo/scripts && cd repo
cat > pyproject.toml <<'P'
[project]
name = "root"
version = "0.1.0"
requires-python = ">=3.9"
dependencies = []
[tool.uv]
no-sources = true
P
# (a parent uv.toml containing `no-sources = true` behaves the same)
cat > scripts/s.py <<'P'
# /// script
# requires-python = ">=3.9"
# dependencies = ["six==1.16.0"]
# ///
import six
print(getattr(six, "SOCKET_PATCHED", 0))
P
uv lock --script scripts/s.py --python 3.11
socket-patch scan scripts --mode vendored --yes --json # exit 0, vendor_prebuilt_downloaded; s.py + s.py.lock wired
uv run --locked --script scripts/s.py # error: the lockfile needs to be updated
uv run --script scripts/s.py # prints 0: unpatched six
# same with `cd scripts && socket-patch scan --mode vendored|hosted`
# control: delete the parent no-sources -> `uv lock --script --check` passes and `uv run --locked` prints 1Expected vs actual
- Expected: docs/testing/uv-compatibility.md (Hosted and vendored uv wiring ignore
no-sources = true: scan and vendor report success,uv sync --lockedthen fails, and a plainuv syncreinstalls the unpatched wheel #564 bullet) says "Both modes refuse it before any file is written: vendored withpypi_uv_no_sources, hosted withredirect_uv_script_unsupported", and "The settings are read where uv reads them". The only exclusions it lists are env vars, flags and a user-leveluv.toml, which "belong to the machine that installs". A committed ancestoruv.toml/[tool.uv]is project data uv reads for the script, so the scan should refuse here the same way it does when the setting sits beside the script. - Actual: exit 0 with the script wired, and the result is the broken or unpatched install Hosted and vendored uv wiring ignore
no-sources = true: scan and vendor report success,uv sync --lockedthen fails, and a plainuv syncreinstalls the unpatched wheel #564 describes.
Matrix (Linux; config discovery is OS-independent TOML logic, so no macOS / Windows probe ran. Probe branches haven't started Actions runs in the last day)
| uv | parent setting | lane | vendored | hosted |
|---|---|---|---|---|
| 0.5.31 | uv.toml |
script | fail (wired; uv run unpatched, even with --locked) |
fail |
| 0.5.31 | pyproject [tool.uv] |
script | fail | fail |
| 0.8.17 | uv.toml / pyproject [tool.uv] |
script | fail (--check exit 1; plain run unpatched) |
— |
| 0.13.0 | uv.toml / pyproject [tool.uv] |
script | fail (×2) | fail |
| 0.13.0 | same, scan scripts from the root |
script | fail | — |
| 0.5.31 / 0.8.17 / 0.13.0 | either | project in subdir | pass (uv ignores the parent) | — |
| 0.5.31 / 0.8.17 / 0.13.0 | none (control) | script | pass (patched, lock check green) | — |
First bad: no earlier release refused this. #1434 introduced the check, and it only looks at the root, so this is a gap in that fix rather than a regression.
Suspect code
crates/socket-patch-core/src/vendor/pypi_lock.rs:248-263: the script lane reads onlyroot.join("pyproject.toml")andpypi_uv::read_uv_toml(root)(pypi_uv.rs:106,root.join(UV_TOML)).crates/socket-patch-core/src/patch/redirect/mod.rs:5951: hosted passes only the root'spyproject.toml/uv.tomlfromfiles.crates/socket-patch-core/src/utils/python_script.rs:400(refuse_uv_no_sources) is fine as is. Its callers would need, for the script lane only, uv's ancestor walk: the nearest directory at or above the script's that holds auv.toml, or apyproject.tomlwith a[tool.uv]table, withuv.tomlwinning in the same directory.
Activity
mikolalysenko commentedon Oct 13, 2026
[agent] Triaged as priority:p1 (uv). Confirmed on main (aa85c6c): the script lane in
vendor/pypi_lock.rsreads only<root>/pyproject.tomland<root>/uv.tomlbeforerefuse_uv_no_sources, and hosted does the same fromfiles, so an ancestoruv.toml/[tool.uv]that uv applies to the script is never seen. Not a duplicate of #564 (that fix, #1434, introduced the root-only check); no open PR covers it.Generated by Claude Code
mikolalysenko commentedon Oct 13, 2026
[agent] Claiming this issue (shared root cause: the uv script lane reads no-sources settings only beside the scanned root instead of walking up to the config uv applies to the script). Branch: agent/fix-uv-script-ancestor-config. Claim-ID: 2026-10-13T20:23:17Z-50101f
Generated by Claude Code
mikolalysenko commentedon Oct 13, 2026
[agent] Draft PR: #1504
Generated by Claude Code
Refuse uv scripts under a parent no-sources
Document the uv script parent config lookup