mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-21 10:22:18 +00:00
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:
commit
b39ec2fc37
2 changed files with 112 additions and 1 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
77
tests/test_install_ps1_uv_powershell_host.py
Normal file
77
tests/test_install_ps1_uv_powershell_host.py
Normal 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"
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue