From fd12e59e6bc97daae914f71cbff11a197387b2dc Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Thu, 18 Jun 2026 22:11:35 +0530 Subject: [PATCH] fix(install): fail fast when uv venv genuinely fails under relaxed EAP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #48372 relaxes EAP=Stop around the uv venv call so PowerShell 5.1 doesn't mistake uv's 'Using CPython ...' stderr for a terminating NativeCommandError. But relaxing EAP also means a *genuine* uv venv failure (exit != 0) no longer aborts on its own — Install-Venv would continue and print 'Virtual environment ready', and in stage mode Invoke-Stage would report ok=true, even though no venv was created. Capture $LASTEXITCODE immediately after the relaxed call and throw on non-zero (Pop-Location first, matching the function's other exit paths), so the venv stage fails fast instead of falsely succeeding. This is the explicit guard originally proposed in #48463 (devorun), composed on top of #48372's reusable helper + regression test. Adds a regression test asserting the uv venv exit-code capture + throw. --- scripts/install.ps1 | 9 ++++++++ tests/test_install_ps1_native_stderr_eap.py | 25 +++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 45bd884c730..7fa28829686 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -1460,6 +1460,15 @@ function Install-Venv { # PowerShell 5.1 with EAP=Stop that stderr is a NativeCommandError unless # we temporarily relax EAP and trust $LASTEXITCODE for real failures. Invoke-NativeWithRelaxedErrorAction { & $UvCmd venv venv --python $PythonVersion } + # Relaxing EAP above means a *genuine* uv-venv failure (exit != 0) no longer + # aborts on its own. Capture $LASTEXITCODE immediately and fail fast, so the + # `venv` stage can't falsely report success (and Invoke-Stage can't emit + # ok=true) when the venv was never created. + $venvExitCode = $LASTEXITCODE + if ($venvExitCode -ne 0) { + Pop-Location + throw "Failed to create virtual environment (uv venv exited with $venvExitCode)" + } # Neutralize any inherited UV_PYTHON (e.g. $env:UV_PYTHON = "3.14" left in # the user's shell). uv honours UV_PYTHON over an existing venv for the diff --git a/tests/test_install_ps1_native_stderr_eap.py b/tests/test_install_ps1_native_stderr_eap.py index 608a282841d..de99bf22900 100644 --- a/tests/test_install_ps1_native_stderr_eap.py +++ b/tests/test_install_ps1_native_stderr_eap.py @@ -54,6 +54,31 @@ def test_uv_venv_and_dependency_installs_relax_eap() -> None: _assert_relaxed_call(text, r"& \$UvCmd pip install -e \$tier\.Spec") +def test_uv_venv_failure_is_not_swallowed_after_eap_relax() -> None: + """Relaxing EAP must not let a genuine `uv venv` failure pass as success. + + Once EAP is relaxed, a real non-zero `uv venv` exit no longer aborts on its + own, so install.ps1 must capture $LASTEXITCODE right after the call and fail + fast — otherwise the `venv` stage falsely reports success (Invoke-Stage emits + ok=true) when no venv was created. Regression guard for the gap caught while + reviewing #48372 (the explicit check originally proposed in #48463). + """ + text = _install_ps1() + # The uv-venv invocation, then an exit-code capture, then a throw — all + # within a small window after the relaxed call. + guard = re.search( + r"& \$UvCmd venv venv --python \$PythonVersion[\s\S]{0,400}?" + r"\$LASTEXITCODE[\s\S]{0,200}?" + r"-ne 0[\s\S]{0,200}?throw", + text, + ) + assert guard is not None, ( + "install.ps1 must capture uv venv's exit code and throw on failure after " + "relaxing ErrorActionPreference, so a genuine venv-creation failure isn't " + "reported as a successful stage" + ) + + def test_native_eap_helper_always_restores_previous_preference() -> None: text = _install_ps1() m = re.search(