fix(pm): honor no_config isolation for bridged index settings in uv runs

stage_runtime (the pinned pm/ self-repair builder) passes no_config=True,
but that only appended uv's --no-config, which removes config FILES. The
pip-mirror bridge (pm.index_config) reaches uv as env vars instead, so a
user's pip.conf index-url still rode into the runtime sync via
runtime_environment() -> _base_environment(env=None). Re-resolving the
official-index pm/uv.lock against a mirror changes the resolution, and
uv sync --locked refuses — breaking the top-level update / doctor /
pm-repair recovery paths on any machine with a pip mirror (#124418).

When no_config is set, strip forwarded index/transport knobs from the
child env: a fully-pinned graph never needs a mirror to resolve, and the
default path keeps the bridge so mirrored/air-gapped application installs
still work.
This commit is contained in:
Yuan Li
2026-09-27 05:05:27 +08:00
committed by Teknium
parent 7a09408e7c
commit 32aeb9ef7b
2 changed files with 39 additions and 0 deletions

View File

@@ -272,6 +272,16 @@ class PythonEnvironment:
# Explicit index credentials survive, but cannot redirect the project,
# interpreter or cache selected by the operation.
env = _base_environment(self.env)
if self.no_config:
# --no-config only removes config files; the pip-mirror bridge
# (pm.index_config) reaches uv as env vars. The pinned runtime
# builder asked to be isolated from ambient index configuration:
# re-resolving its official-index lockfile against a mirror trips
# `--locked` (#124418), and a fully-pinned graph never needs a
# mirror to resolve.
from pm.index_config import is_forwarded
env = {key: value for key, value in env.items() if not is_forwarded(key)}
env.update(UV_PYTHON=str(self.python), UV_PROJECT_ENVIRONMENT=str(self.destination),
UV_CACHE_DIR=str(self.cache), UV_PYTHON_DOWNLOADS="never")
with tempfile.TemporaryDirectory(prefix="pm-uv-config-") as config:

View File

@@ -787,3 +787,32 @@ def test_live_apply_keeps_selection_on_failed_union(locked_project, tmp_path, mo
assert set(generations.iterdir()) == prior_generations
assert (source / "uv.lock").read_bytes() == source_lock
assert env == original_env
def test_no_config_drops_bridged_index_settings_from_child_env(tmp_path):
"""no_config must isolate the pinned runtime builder from ALL ambient uv
configuration: --no-config covers config files, but PM's pip-mirror bridge
injects UV_INDEX_URL as an env var, and re-resolving the official-index
runtime lockfile against that mirror trips `uv sync --locked` (#124418).
The default path keeps the bridge — mirrored networks need it to resolve
their application dependencies at all."""
import io
from pm.environment import PythonEnvironment
ambient = dict(os.environ, UV_INDEX_URL="https://fixture-mirror.invalid/simple",
UV_HTTP_TIMEOUT="99")
child = ("import os, sys\n"
"present = sorted(k for k in ('UV_INDEX_URL', 'UV_HTTP_TIMEOUT') if k in os.environ)\n"
"sys.exit(', '.join(present) if present else None)\n")
def run(**kwargs):
environment = PythonEnvironment(
uv=Path(sys.executable), python=Path(sys.executable), destination=tmp_path / "venv",
cache=tmp_path / "cache", env=ambient, output=io.StringIO(), **kwargs)
return environment._run(["-c", child], cwd=tmp_path, timeout=60)
isolated = run(no_config=True)
assert isolated.returncode == 0, f"bridged settings leaked into no_config child: {isolated.stderr}"
bridged = run()
assert bridged.returncode == 1, "default path must keep the bridge"
assert "UV_INDEX_URL" in bridged.stderr and "UV_HTTP_TIMEOUT" in bridged.stderr