Skip to content

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

Closed

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 hosted exit 0 (hosted reports no warning at all) and rewrite s.py + s.py.lock.
  • uv lock --script s.py --check and uv run --locked --script s.py fail ("The lockfile … needs to be updated").
  • A plain uv run --script s.py re-resolves from PyPI and imports the unpatched module. On uv 0.5.31, even uv run --locked silently 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 1

Expected vs actual

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 only root.join("pyproject.toml") and pypi_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's pyproject.toml / uv.toml from files.
  • 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 a uv.toml, or a pyproject.toml with a [tool.uv] table, with uv.toml winning in the same directory.

Activity

  1. mikolalysenko commented on Oct 13, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triaged as priority:p1 (uv). Confirmed on main (aa85c6c): the script lane in vendor/pypi_lock.rs reads only <root>/pyproject.toml and <root>/uv.toml before refuse_uv_no_sources, and hosted does the same from files, so an ancestor uv.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

  2. mikolalysenko commented on Oct 13, 2026

    @mikolalysenko
    CollaboratorAuthor

    [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

  3. mikolalysenko commented on Oct 13, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft PR: #1504


    Generated by Claude Code

  4. added 2 commits that reference this issue on Oct 13, 2026
    de98889
    c81db93
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