Merge pull request #48341 from xxxigm/fix/install-ps1-powershell-host-resolution

fix(install): resolve PowerShell host instead of bare `powershell` for uv install
This commit is contained in:
kshitij 2026-06-18 21:09:50 +05:30 committed by GitHub
commit b39ec2fc37
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 112 additions and 1 deletions

View file

@ -318,6 +318,36 @@ function Install-AgentBrowser {
# Dependency checks
# ============================================================================
# Resolve the PowerShell host executable used to spawn child PowerShell
# processes (the astral uv installer below). We must NOT hardcode the bare
# name `powershell`: it names *Windows PowerShell* and only resolves when its
# System32 directory is on PATH. When install.ps1 is run under PowerShell 7+
# (`pwsh`) -- or any session where `powershell` isn't on PATH -- a bare
# `powershell` spawn dies with "The term 'powershell' is not recognized",
# aborting uv installation (field report: Windows install stuck, uv install
# failed with exactly that message). Prefer the absolute path of the host we
# are already running in (PATH-independent), then fall back to whichever of
# powershell/pwsh is resolvable, and only then to the bare name.
function Get-PowerShellHostExe {
try {
$hostExe = (Get-Process -Id $PID).Path
if ($hostExe -and (Test-Path $hostExe)) {
$leaf = Split-Path $hostExe -Leaf
# Only trust the current host when it is a real PowerShell CLI
# (not e.g. powershell_ise.exe or an embedded host that can't take
# `-ExecutionPolicy`/`-Command`).
if ($leaf -match '^(?i:powershell|pwsh)\.exe$') { return $hostExe }
}
} catch { }
foreach ($candidate in @("powershell", "pwsh")) {
$cmd = Get-Command $candidate -CommandType Application -ErrorAction SilentlyContinue |
Select-Object -First 1
if ($cmd -and $cmd.Source) { return $cmd.Source }
}
# Last-ditch: hand back the bare name so the spawn surfaces its own error.
return "powershell"
}
function Install-Uv {
# Hermes owns its own uv at $HermesHome\bin\uv.exe. Always install there —
# no PATH probing, no conda guards, no multi-location resolution chains.
@ -341,7 +371,11 @@ function Install-Uv {
try {
$ErrorActionPreference = "Continue"
$env:UV_INSTALL_DIR = Join-Path $HermesHome "bin"
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" 2>&1 | Out-Null
# Spawn via the resolved host exe (see Get-PowerShellHostExe) rather
# than a bare `powershell`, which isn't guaranteed to be on PATH under
# PowerShell 7 / pwsh-only setups.
$psHostExe = Get-PowerShellHostExe
& $psHostExe -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" 2>&1 | Out-Null
$ErrorActionPreference = $prevEAP
if (Test-Path $managedUv) {

View file

@ -0,0 +1,77 @@
"""Regression: the Windows installer must not spawn a bare ``powershell``.
A user on Windows reported the installer getting stuck; running
``irm https://hermes-agent.nousresearch.com/install.ps1 | iex`` failed at the
uv step with::
[X] Failed to install uv: The term 'powershell' is not recognized as the
name of a cmdlet, function, script file, or operable program.
Root cause: ``Install-Uv`` spawned the astral uv installer via a hardcoded
bare ``powershell`` command. That name resolves only to *Windows PowerShell*
and only when its System32 directory is on ``PATH``. Under PowerShell 7+
(``pwsh``) -- or any session where ``powershell`` isn't on ``PATH`` -- the
spawn dies and uv installation aborts.
The fix resolves the PowerShell host executable (preferring the absolute path
of the running host, then ``powershell``/``pwsh`` via ``Get-Command``) and
invokes *that* instead of a bare name. These tests lock that contract at the
source level (the script only runs on Windows, so there's no runner to
execute it on Linux CI).
"""
from pathlib import Path
import pytest
_INSTALL_PS1 = Path(__file__).resolve().parents[1] / "scripts" / "install.ps1"
@pytest.fixture(scope="module")
def source() -> str:
return _INSTALL_PS1.read_text(encoding="utf-8")
def test_astral_uv_installer_not_spawned_via_bare_powershell(source: str):
"""The exact failing literal must be gone."""
forbidden = 'powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv'
assert forbidden not in source, (
"Install-Uv still spawns the astral uv installer via a bare "
"`powershell` — it must use the resolved PowerShell host exe so it "
"works under pwsh / when powershell isn't on PATH."
)
def test_astral_uv_installer_invoked_via_resolved_host_variable(source: str):
"""The astral uv installer line must use the call operator on a variable.
i.e. ``& $psHostExe -ExecutionPolicy ... irm https://astral.sh/uv...``
rather than naming a fixed executable.
"""
lines = [ln for ln in source.splitlines() if "astral.sh/uv/install.ps1 | iex" in ln]
# Exactly one invocation line carries the astral installer.
invocation = [ln for ln in lines if "irm https://astral.sh/uv/install.ps1 | iex" in ln]
assert invocation, "astral uv install invocation line not found"
for ln in invocation:
stripped = ln.strip()
assert stripped.startswith("& $"), (
f"astral uv installer must be invoked via the call operator on a "
f"resolved host variable (`& $...`), got: {stripped!r}"
)
def test_powershell_host_resolver_is_defined_and_portable(source: str):
"""A host-resolver helper must exist and be PATH-independent + pwsh-aware."""
assert "function Get-PowerShellHostExe" in source, (
"expected a Get-PowerShellHostExe helper that resolves the host exe"
)
# PATH-independent: derive the absolute path of the running host.
assert "Get-Process -Id $PID" in source, (
"resolver must derive the current host's absolute path "
"(Get-Process -Id $PID), which is independent of PATH"
)
# pwsh-aware fallback: PowerShell 7's executable is `pwsh`, not `powershell`.
assert "pwsh" in source, (
"resolver must fall back to pwsh (PowerShell 7) when powershell is "
"unavailable"
)