Repository navigation
Fix uv project shadowing parent uv settings (#1545) - #1566
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A uv project whose pyproject.toml has no [tool.uv] table takes its settings (exclude-newer, resolution, indexes) from a parent uv.toml or [tool.uv]. Hosted and vendored scans added [tool.uv.sources] to such a project, which made uv read the project's pyproject instead and drop every parent setting: `uv sync --locked` then failed, and a plain `uv lock` re-resolved without them, while the scan reported success. Both modes now look up the parent settings file the way uv does and refuse before writing anything, naming that file: hosted with redirect_uv_project_unsupported, vendored with the new pypi_uv_ancestor_settings. A parent that holds only another project's metadata (a workspace root's workspace or sources table) sets nothing the project inherits and is still wired. Fixes #1545 Assisted-by: Claude Code:claude-opus-5-5
A parent's override-dependencies and constraint-dependencies never reach a child project's lock (checked with uv 0.11), so a monorepo root that sets only those no longer blocks patching the projects below it. The refusal no longer claims every dropped setting breaks `uv sync --locked`, the docs no longer say only scripts read a parent's settings, the script and project lanes share one way to name the parent file, and the CLI test now runs vendored mode under a parent that must not be refused. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ 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 a754663. Configure here.
|
Ready for review at
Generated by Claude Code |
Final review brief: #1566 at
|
|
[agent] On the first open question: since Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1545
Summary
A uv project whose own
pyproject.tomlhas no[tool.uv]table (and nouv.tomlbeside it) takes uv's settings from a parentuv.tomlor[tool.uv]. Hosted and vendoredscanadded[tool.uv.sources]to such a project, which made uv read the project's pyproject instead and drop every parent setting.uv sync --lockedthen failed, a plainuv lockre-resolved without the settings, and the scan still reportedsuccess.Both modes now refuse before writing anything and name the parent file:
redirect_uv_project_unsupported, the code it already uses for uv project shapes it can't wirepypi_uv_ancestor_settingscodeThis takes the issue's safer option (refuse) over copying the parent's settings into the project, which would change semantics.
Root cause
uv looks up a project's settings the same way it looks up a PEP 723 script's: the nearest directory at or above it with a
uv.tomlor apyproject.tomlholding[tool.uv]. #1501 / #1504 added that lookup (find_ancestor_uv_config) but used it only in the script lane, on the assumption that "a project stops that lookup at itself". That only holds once the project has[tool.uv], and here socket-patch is what adds it.Changes
utils/python_script.rs: newrefuse_uv_project_shadowing(pyproject, uv_toml, ancestor). It refuses when the root sets no uv settings and the nearest ancestor settings file sets something the project inherits.workspace,sources,managed,dev-dependencies,override-/constraint-dependenciesand so on) don't count. A monorepo root that is only a workspace root still allows patching.hosted/engine.rs: the ancestor settings file is now read for theuv.locklane too, not only for script locks.patch/redirect/mod.rsruns the new check on the project lane.vendor/pypi_uv.rs:load_uv_projectrecords the ancestor andcheck_target_guardsrefuses withpypi_uv_ancestor_settings. These guards run before the wheel is built, so a refusal writes nothing.docs/testing/uv-compatibility.md: documents the project lane.No wrapper changes (
npm/,pypi/,gem/only dispatch to the binary).Test evidence
Red to green (each new test failed against a no-op
refuse_uv_project_shadowingand passes with the fix):utils::python_script::ancestor_config_tests::project_refuses_to_shadow_an_ancestors_settingspatch::redirect::python_metadata_pairing_tests::ancestor_settings_refuse_a_project_that_would_shadow_themvendor::pypi_uv::tests::guards_refuse_shadowing_a_parents_uv_settingsuv.tomland parent[tool.uv]mode_migration_pypi::uv_project_under_parent_settings_is_refused_in_both_modesThe CLI test also checks that a parent holding only
[tool.uv.workspace]is still wired in hosted and vendored mode.With real uv 0.11.32 I confirmed the mechanism and the exemption:
uv.toml(resolution), adding[tool.uv]to the child makesuv lock --lockedfail[tool.uv.workspace]stays greenconstraint-dependencies/override-dependenciesnever reach the child's lock (in a child-only control they do)Commands run locally:
cargo fmt --all -- --check: okcargo clippy --workspace --all-features -- -D warnings: okcargo test --workspace --all-features --no-fail-fast: everything passes except 10 tests that fail only in this sandbox, none in uv code:chmod 0o555, which root ignores here:in_process_redirectx3,repairx2, and corecopy_tree/vlt_heal/pypi_poetry/pypi_requirementse2e_bun_lockb::binary_shared_bundled_record_hosted_pin_is_managede2e_redirect_uv_buildande2e_vendor_pypi_buildwith real uv 0.11.32 (--include-ignored): 23/23 and 55/55/code-review high findings
Fixed:
override-/constraint-/build-constraint-/exclude-dependenciesto the project-only keys (verified with real uv)--lockedUvConfig::settings_location)Not fixed, and why:
cache-dir,[pip],keyring-provider, ...): kept on purpose. Dropping any inherited setting silently changes how uv installs the project, and some of them (keyring-provider,allow-insecure-host,required-version) break installs even though--lockedwould pass. The message tells the user how to opt in: give the project its own[tool.uv].uv sync --locked(it is the uv project under a parent uv.toml / [tool.uv]: hosted and vendored scan add a [tool.uv] table to the project's pyproject.toml, which hides the parent settings from uv, souv sync --lockedfails and a plainuv lockre-resolves without them #1545 bug). The run restores its snapshot and names the parent file.rollbackrestores the pre-wiring pyproject.uv.tomlis treated as absent: this matches the existing Hosted and vendored uv wiring ignoreno-sources = true: scan and vendor report success,uv sync --lockedthen fails, and a plainuv syncreinstalls the unpatched wheel #564 / uv PEP 723 script in a subdirectory: hosted and vendored wiring ignoreno-sources = truefrom a parentuv.toml/pyproject.toml, souv run --lockedfails and a plainuv runimports the unpatched package #1501 handling (read_uv_tomlis advisory), and uv reports an unreadableuv.tomlitself.load_uv_project: it's a few stat/read calls up the tree, the same as the script lane's existing walk invendor/pypi_lock.rs. Not worth an asyncify or a cache.🤖 Generated with Claude Code
Generated by Claude Code