Skip to content

Fix uv script no-sources in a parent config (#1501) - #1504

Merged
Mikola Lysenko (mikolalysenko) merged 6 commits into
mainfrom
agent/fix-uv-script-ancestor-config
Oct 13, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 6 commits into
mainfrom
agent/fix-uv-script-ancestor-config

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #1501

Summary

A PEP 723 script scanned from a subdirectory (say scripts/ under a uv project whose root pyproject.toml sets [tool.uv] no-sources = true) was wired in both modes with exit 0. uv applies the parent's settings to the script, so it ignored the wired source: uv run --locked --script failed, and a plain uv run imported the unpatched package. Both modes now refuse it before writing anything, the same way they do when the setting sits beside the script. The refusal names the parent file:

  • vendored: pypi_uv_no_sources, e.g. job.py: ../uv.toml sets no-sources = true, ...
  • hosted: redirect_uv_script_unsupported (skipped with a warning, as with the other uv script shapes hosted can't wire)

Root cause

#1434 (the #564 fix) read uv's settings only beside the scanned root. For a script, uv uses the nearest directory at or above it that holds a uv.toml, or a pyproject.toml with a [tool.uv] table, checking uv.toml first in each directory. A root pyproject.toml without [tool.uv] doesn't stop that lookup.

Changes

  • utils::python_script: find_ancestor_uv_config walks above the root the way uv does. It uses the FIFO-safe regular-file reader and stops at a settings file that exists but can't be read. root_governs_script decides whether the walk is needed at all. refuse_uv_script_no_sources applies the ancestor's settings only when the root sets none. The shared check now carries the file's label, so messages name ../uv.toml or ../pyproject.toml [tool.uv].
  • Vendored (vendor/pypi_lock.rs): the script lane uses the walk.
  • Hosted: the engine walks above a disk root only when the root doesn't govern. It registers every probed path with the snapshot read tracker (disk_root_reading) and hands the file to the planner under a new synthetic key, UV_ANCESTOR_CONFIG_KEY. The in-memory engine has no "above", so it is unchanged.
  • The project lane (uv.lock) is unchanged. The issue's matrix shows uv 0.5.31, 0.8.17 and 0.13.0 all ignore a parent config for a project in a subdirectory.
  • docs/testing/uv-compatibility.md: the 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 bullet now describes the parent lookup.

Tests (red → green)

  • e2e mode_migration_pypi::uv_script_no_sources_in_a_parent_is_refused_in_both_modes covers a parent uv.toml, a parent pyproject [tool.uv] no-sources, a parent no-sources-package, and the control (a parent pyproject without [tool.uv] is still wired). Red on main with the core fix stashed: hosted uv.toml pinned the script, panicking at mode_migration_pypi.rs:900. Green with the fix, and the whole suite passes (62/62).
  • core unit tests: python_script::ancestor_config_tests (lookup order, uv.toml before pyproject, skipping a pyproject without [tool.uv], .. in the root, stopping at an unreadable file, the root's settings and the script's own setting winning), pypi_lock::script_no_sources_in_a_parent_refuses_before_writing, redirect::no_sources_above_the_root_refuses_a_script. The existing 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 tests still pass.
Issue Test
#1501 vendored script_no_sources_in_a_parent_refuses_before_writing, e2e vendored half
#1501 hosted no_sources_above_the_root_refuses_a_script, e2e hosted half

Local: cargo fmt --all -- --check and cargo clippy --workspace --all-features -- -D warnings are clean. cargo test --workspace --all-features --no-fail-fast: 246 test binaries pass. 13 tests fail only because this sandbox runs as root: they inject write failures with chmod 0o555/0o600 (covgap_commands_vendor, repair, vlt heal, poetry/requirements write-failure tests, copy_tree symlinked root, one e2e_bun_lockb rollback). None touch the code in this PR, and CI is the judge. The npm/pypi/gem wrappers only dispatch, so they need no change.

/code-review high

Fixed:

  • FIFO-safe reads, and stopping at an unreadable settings file.
  • Skipping the walk when the root already governs, which also keeps unrelated ancestor files out of the snapshot cache's fingerprints.
  • The uv.toml vs pyproject kind now comes from the file name.

Not changed:

  • Project lane should walk too. This contradicts the issue's tested matrix: uv ignores the parent for a subdirectory project on all three tested versions.
  • uv looks up from its working directory, not the script's directory. The scan can't know where uv run will be invoked. The issue's repro and matrix (cd scripts, and scan scripts from the root) match the script-directory model.
  • Symlinked root: logical vs. physical parents. This is an edge case, left as a follow-up if it shows up.
  • Reuse the NuGet ancestor walk. It has different semantics (it merges every level), so it isn't a fit.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CP8zKCXPGdH2FsXSCPx4E4


Note

Medium Risk
Changes PEP 723 script wiring/refusal in vendored and hosted paths; behavior is narrower (more refusals) but touches redirect and lock loading logic.

Overview
Fixes #1501: PEP 723 scripts scanned from a subdirectory (e.g. scripts/) were still wired when a parent uv.toml or pyproject.toml [tool.uv] set no-sources / no-sources-package, even though uv applies those settings to the script and would ignore wired sources.

Vendored and hosted now walk ancestor config the way uv does when the scan root has no governing [tool.uv] / uv.toml (root_governs_script). Refusal happens before any writes, naming the parent file (e.g. ../uv.toml sets no-sources = true): vendored pypi_uv_no_sources, hosted redirect_uv_script_unsupported.

Hosted disk scans attach the discovered parent settings via synthetic UV_ANCESTOR_CONFIG_KEY; the redirect planner uses refuse_uv_script_no_sources. The uv.lock project lane is unchanged. Docs and e2e/unit tests cover parent configs, workspace-member guard, and lookup order.

Reviewed by Cursor Bugbot for commit 11a9e45. Configure here.


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
A PEP 723 script takes uv's settings from the nearest directory at or
above it holding a uv.toml or a pyproject.toml with [tool.uv]. When a
script was scanned from a subdirectory (scripts/ under a project whose
root sets no-sources = true), both modes read only the scanned root,
wired the script and exited 0. uv then ignored the source: `uv run
--locked` failed and a plain `uv run` imported the unpatched package.

The script lane now walks above the root when the root sets nothing,
the way uv does, and refuses with the parent file named (vendored
pypi_uv_no_sources, hosted redirect_uv_script_unsupported). The
project lane is unchanged, since a project stops uv's lookup at itself.

Fixes #1501

Assisted-by: Claude Code:claude-opus-5-5
Note in the #564 bullet that a script's project settings come from the
nearest uv.toml or [tool.uv] at or above it (#1501).

Assisted-by: Claude Code:claude-opus-5-5
The parent lookup now skips the walk when the scanned root's own
uv.toml or [tool.uv] already governs the script, opens each file
through the FIFO-safe regular-file reader, and stops at a settings
file that exists but cannot be read, the way uv does, instead of
going on to a farther one.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread crates/socket-patch-core/src/utils/python_script.rs Outdated
uv warns about a pyproject.toml it cannot parse during settings
discovery and goes on to the parent directory. The script lookup
treated such a file as governing, so a broken pyproject beside the
script or in between hid a parent uv.toml that sets no-sources, and
the script was wired anyway.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread crates/socket-patch-core/src/utils/python_script.rs
In a uv workspace, uv takes a member's script settings from the
workspace root, so a member's own [tool.uv] does not shield the
script from the root's no-sources. The #1138 workspace-member guard
already refuses that run in both modes before the script lookup; this
pins it.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 11a9e45. Configure here.

Merged via the queue into main with commit 43638b2 Oct 13, 2026
54 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-uv-script-ancestor-config branch October 14, 2026 00:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants