# NousResearch/hermes-agent · PowerShell EAP=Stop causing spurious install failures

## Open now

- Nothing open on GitHub.

These issues and PRs address a Windows install.ps1 problem where PowerShell 5.1's ErrorActionPreference=Stop incorrectly treats stderr output from native git/uv calls as fatal errors. The proposed fixes relax error handling around those native calls to prevent spurious installation failures.

## TL;DR

- 4 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #48529. Verify it addresses your case.
- **Recommended:** read PR #48529 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `unresolved`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.50`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #48352: install.ps1 (Windows): PowerShell 5.1 NativeCommandError wrapping of git/uv stderr causes spurious install failures

## Prior & competing PRs

- **#48529: fix(install): relax EAP=Stop around native git/uv calls + fail-fast on uv venv failure (#48352, salvage of #48372)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `scripts/install.ps1`, `tests/test_install_ps1_native_stderr_eap.py`
  - fix hunks:
  - `scripts/install.ps1`

    ```diff
    @@ -185,6 +185,18 @@ function Write-Err {
         Write-Host "[X] $Message" -ForegroundColor Red
     }
     
    +function Invoke-NativeWithRelaxedErrorAction {
    +    param([scriptblock]$Script)
    +
    +    $prevEAP = $ErrorActionPreference
    +    $ErrorActionPreference = "Continue"
    +    try {
    +        & $Script
    +    } finally {
    +        $ErrorActionPreference = $prevEAP
    +    }
    +}
    +
     # Inspect npm output for a TLS-trust failure and, if found, print actionable
     # remediation. npm/Node surface corporate MITM proxies and missing root CAs as
     # "unable to get local issuer certificate" / "self-signed certificate in
    ```

    ```diff
    @@ -1306,7 +1318,7 @@ function Install-Repository {
             Write-Info "Trying SSH clone..."
             $env:GIT_SSH_COMMAND = "ssh -o BatchMode=yes -o ConnectTimeout=5"
             try {
    -            git -c windows.appendAtomically=false clone --depth 1 --branch $Branch $RepoUrlSsh $InstallDir
    +            Invoke-NativeWithRelaxedErrorAction { git -c windows.appendAtomically=false clone --depth 1 --branch $Branch $RepoUrlSsh $InstallDir }
                 if ($LASTEXITCODE -eq 0) { $cloneSuccess = $true }
             } catch { }
             $env:GIT_SSH_COMMAND = $null
    ```

    ```diff
    @@ -1315,7 +1327,7 @@ function Install-Repository {
                 if (Test-Path $InstallDir) { Remove-Item -Recurse -Force $InstallDir -ErrorAction SilentlyContinue }
                 Write-Info "SSH failed, trying HTTPS..."
                 try {
    -                git -c windows.appendAtomically=false clone --depth 1 --branch $Branch $RepoUrlHttps $InstallDir
    +                Invoke-NativeWithRelaxedErrorAction { git -c windows.appendAtomically=false clone --depth 1 --branch $Branch $RepoUrlHttps $InstallDir }
                     if ($LASTEXITCODE -eq 0) { $cloneSuccess = $true }
                 } catch { }
             }
    ```

    ```diff
    @@ -1443,8 +1455,20 @@ function Install-Venv {
             Remove-Item -Recurse -Force "venv"
         }
         
    -    # uv creates the venv and pins the Python version in one step
    -    & $UvCmd venv venv --python $PythonVersion
    +    # uv creates the venv and pins the Python version in one step.  uv emits
    +    # normal progress such as "Using CPython ..." on stderr; under Windows
    +    # 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
    @@ -1514,7 +1538,7 @@ function Install-Dependencies {
             # in the wrong directory and imports fail with ModuleNotFoundError.
             # (Mirrors the same flag in scripts/install.sh::install_deps.)
             $env:UV_PROJECT_ENVIRONMENT = "$InstallDir\venv"
    -        & $UvCmd sync --extra all --locked
    +        Invoke-NativeWithRelaxedErrorAction { & $UvCmd sync --extra all --locked }
             if ($LASTEXITCODE -eq 0) {
                 Write-Success "Main package installed (hash-verified via uv.lock)"
                 $script:InstalledTier = "hash-verified (uv.lock)"
    ```

    ```diff
    @@ -1589,7 +1613,7 @@ except Exception:
         if (-not $skipPipFallback) {
             foreach ($tier in $installTiers) {
             Write-Info "Trying tier: $($tier.Name) ..."
    -        & $UvCmd pip install -e $tier.Spec
    +        Invoke-NativeWithRelaxedErrorAction { & $UvCmd pip install -e $tier.Spec }
             if ($LASTEXITCODE -eq 0) {
                 Write-Success "Main package installed ($($tier.Name))"
                 $script:InstalledTier = $tier.Name
    ```
  - `tests/test_install_ps1_native_stderr_eap.py`

    ```diff
    @@ -0,0 +1,94 @@
    +"""Regression tests for #48352: Windows PowerShell 5.1 native stderr.
    +
    +PowerShell 5.1 turns stderr from native commands into ``NativeCommandError``
    +records when ``$ErrorActionPreference = "Stop"``.  ``scripts/install.ps1`` has a
    +few git/uv calls where stderr can be normal progress output, so those calls must
    +run with EAP temporarily relaxed and then inspect ``$LASTEXITCODE``.
    +"""
    +
    +from __future__ import annotations
    +
    +import re
    +from pathlib import Path
    +
    +REPO_ROOT = Path(__file__).resolve().parent.parent
    +INSTALL_PS1 = REPO_ROOT / "scripts" / "install.ps1"
    +
    +
    +def _install_ps1() -> str:
    +    return INSTALL_PS1.read_text(encoding="utf-8")
    +
    +
    +def _assert_relaxed_call(text: str, command_pattern: str) -> None:
    +    helper_block_pattern = (
    +        r"Invoke-NativeWithRelaxedErrorAction\s*\{[^}]*"
    +        + command_pattern
    +        + r"[^}]*\}"
    +    )
    +    inline_pattern = (
    +        r"\$ErrorActionPreference\s*=\s*\"Continue\"[\s\S]{0,900}?"
    +        + command_pattern
    +    )
    +    assert re.search(helper_block_pattern, text) or re.search(inline_pattern, text), (
    +        f"install.ps1 must relax ErrorActionPreference around {command_pattern}"
    +    )
    +
    +
    +def test_repository_stage_relieves_eap_for_ssh_and_https_git_clone() -> None:
    +    text = _install_ps1()
    +    assert "function Invoke-NativeWithRelaxedErrorAction" in text
    +    _assert_relaxed_call(
    +        text,
    +        r"git -c windows\.appendAtomically=false clone --depth 1 --branch \$Branch \$RepoUrlSsh \$InstallDir",
    +    )
    +    _assert_relaxed_call(
    +        text,
    +        r"git -c windows\.appendAtomically=false clone --depth 1 --branch \$Branch \$RepoUrlHttps \$InstallDir",
    +    )
    +
    +
    +def test_uv_venv_and_dependency_installs_relax_eap() -> None:
    +    text = _install_ps1()
    +    _assert_relaxed_call(text, r"& \$UvCmd venv venv --python \$PythonVersion")
    +    _assert_relaxed_call(text, r"& \$UvCmd sync --extra all --locked")
    +    _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(
    +        r"function Invoke-NativeWithRelaxedErrorAction \{(?P<body>[\s\S]*?)^\}",
    +        text,
    +        re.MULTILINE,
    +    )
    +    assert m is not None, "expected a shared helper for NativeCommandError-safe calls"
    +    body = m.group("body")
    +    assert "$prevEAP = $ErrorActionPreference" in body
    +    assert '$ErrorActionPreference = "Continue"' in body
    +    assert "finally" in body
    +    assert "$ErrorActionPreference = $prevEAP" in body
    ```
- **#48372: fix(install): relax native stderr handling in install.ps1 (#48352)**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#48529`, `#48352`
- **#48463: fix(install): relax EAP=Stop around git/uv native calls in install.ps1 (#48352)**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#48529`, `#48372`

## Fix status

- Merged to main in PR #48529 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-06-18`.
