Windows installer Python detection and venv fixes

NousResearch/hermes-agent · cluster 24424 · hermes cli

Data as of

Nothing open on GitHub. Fix merged in #26586. Check it covers your case.

The short answer

0open issues
0open PRs
8items grouped
Known?
Yes. 8 related items grouped here, 2 issues already closed.
Fix event
A fix was merged to main in PR #26586. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PR #51602. That is separate from the fix event.
Still open
Nothing open on GitHub.

These issues and fixes address Windows-specific installer failures related to Python detection, including skipping Microsoft Store stub executables, re-resolving the Python fallback at venv creation time, and handling missing Python installations.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #26586 fix(install.ps1): restore EAP=Continue around uv python install, skip Store stub

    merged to main

    files: scripts/install.ps1

    Show the diff
    • scripts/install.ps1
      @@ -145,19 +145,39 @@ function Test-Python {
           # Python not found — use uv to install it (no admin needed!)
           Write-Info "Python $PythonVersion not found, installing via uv..."
           try {
      +        # Temporarily relax ErrorActionPreference: uv writes download progress
      +        # ("Downloading cpython-3.11.15-windows-x86_64-none (24.5MiB)") to
      +        # stderr.  With $ErrorActionPreference = "Stop" (set at the top of this
      +        # script) PowerShell wraps stderr lines from native commands as
      +        # ErrorRecord objects when captured via 2>&1, then throws a terminating
      +        # exception on the first one — even though uv exits 0 and Python was
      +        # installed successfully.  Verify success via `uv python find`
      +        # afterwards, which is the reliable signal regardless of exit-code
      +        # semantics or stderr noise.  This fix was previously landed as
      +        # commit ec1714e71 and then lost in a release squash; reapplied here.
      +        $prevEAP = $ErrorActionPreference
      +        $ErrorActionPreference = "Continue"
               $uvOutput = & $UvCmd python install $PythonVersion 2>&1
      -        if ($LASTEXITCODE -eq 0) {
      -            $pythonPath = & $UvCmd python find $PythonVersion 2>$null
      -            if ($pythonPath) {
      -                $ver = & $pythonPath --version 2>$null
      -                Write-Success "Python installed: $ver"
      -                return $true
      -            }
      -        } else {
      +        $uvExitCode = $LASTEXITCODE
      +        $ErrorActionPreference = $prevEAP
      +
      +        # Check if Python is now available (more reliable than exit code
      +        # since uv may return non-zero due to "already installed" etc.)
      +        $pythonPath = & $UvCmd python find $PythonVersion 2>$null
      +        if ($pythonPath) {
      +            $ver = & $pythonPath --version 2>$null
      +            Write-Success "Python installed: $ver"
      +            return $true
      +        }
      +
      +        # uv ran but Python still not findable — show what happened
      +        if ($uvExitCode -ne 0) {
                   Write-Warn "uv python install output:"
                   Write-Host $uvOutput -ForegroundColor DarkGray
               }
           } catch {
      +        # Restore EAP in case the try block threw before the assignment
      +        if ($prevEAP) { $ErrorActionPreference = $prevEAP }
               Write-Warn "uv python install error: $_"
           }
       
      @@ -175,15 +195,42 @@ function Test-Python {
               } catch { }
           }
       
      -    # Fallback: try system python
      -    if (Get-Command python -ErrorAction SilentlyContinue) {
      -        $sysVer = python --version 2>$null
      -        if ($sysVer -match "3\.(1[0-9]|[1-9][0-9])") {
      -            Write-Success "Using system Python: $sysVer"
      -            return $true
      +    # Fallback: try system python — but skip the Microsoft Store stub.
      +    # On Windows, %LOCALAPPDATA%\Microsoft\WindowsApps\python.exe is a 0-byte
      +    # reparse-point stub that prints "Python was not found; run without
      +    # arguments to install from the Microsoft Store..." to stdout and exits
      +    # non-zero.  Get-Command finds it; invoking it produces a confusing error
      +    # that the user sees as our installer crashing.
      +    $pythonCmd = Get-Command python -ErrorAction SilentlyContinue
      +    if ($pythonCmd) {
      +        $isStoreStub = $false
      +        try {
      +            $pythonSource = $pythonCmd.Source
      +            if ($pythonSource -and $pythonSource -like "*\WindowsApps\*") {
      +                $isStoreStub = $true
      +            } else {
      +                # Even outside WindowsApps, a 0-byte file is the stub
      +                $item = Get-Item $pythonSource -ErrorAction SilentlyContinue
      +                if ($item -and $item.Length -eq 0) { $isStoreStub = $true }
      +            }
      +        } catch { }
      +
      +        if (-not $isStoreStub) {
      +            try {
      +                $prevEAP2 = $ErrorActionPreference
      +                $ErrorActionPreference = "Continue"
      +                $sysVer = & python --version 2>&1
      +                $ErrorActionPreference = $prevEAP2
      +                if ($sysVer -match "Python 3\.(1[0-9]|[1-9][0-9])") {
      +                    Write-Success "Using system Python: $sysVer"
      +                    return $true
      +                }
      +            } catch {
      +                if ($prevEAP2) { $ErrorActionPreference = $prevEAP2 }
      +            }
               }
           }
      -    
      +
           Write-Err "Failed to install Python $PythonVersion"
           Write-Info "Install Python 3.11 manually, then re-run this script:"
           Write-Info "  https://www.python.org/downloads/"
  • #51602 fix(installer): re-resolve Python fallback at venv stage on Windows (#50769)

    merged to main

    files: scripts/install.ps1, tests/test_install_ps1_python_fallback_venv.py

    Show the diff
    • scripts/install.ps1
      @@ -139,6 +139,11 @@ foreach ($tmpVar in @('TEMP', 'TMP')) {
       $RepoUrlSsh = "[redacted:email]:NousResearch/hermes-agent.git"
       $RepoUrlHttps = "https://github.com/NousResearch/hermes-agent.git"
       $PythonVersion = "3.11"
      +# Minor versions the installer accepts when the requested $PythonVersion isn't
      +# available, in preference order.  uv discovers both uv-managed and system
      +# interpreters, so this list also matches a pre-existing system Python.  Single
      +# source of truth shared by Test-Python's fallback and Resolve-AvailablePythonVersion.
      +$PythonFallbackVersions = @("3.12", "3.13", "3.10")
       $NodeVersion = "22"
       
       # Stage-protocol version.  Bumped only for genuinely breaking changes to the
      @@ -510,6 +515,31 @@ function Resolve-UvCmd {
           throw "uv is not installed. Run install.ps1 -Stage uv first."
       }
       
      +function Resolve-AvailablePythonVersion {
      +    # Return the first Python minor version uv can actually find, preferring the
      +    # requested $PythonVersion and then $PythonFallbackVersions.  Returns $null
      +    # when none are available.
      +    #
      +    # This is the cross-process-safe counterpart to Test-Python's in-memory
      +    # ``$script:PythonVersion = $fallbackVer`` mutation.  Under Hermes-Setup.exe
      +    # each ``-Stage NAME`` runs in a *fresh* powershell.exe, so the fallback the
      +    # ``python`` stage settled on (e.g. 3.12 when 3.11 is absent) does NOT
      +    # survive into the ``venv`` stage's process -- there $PythonVersion is back
      +    # at its "3.11" default.  Consumers re-resolve here instead of trusting that
      +    # default, which is exactly the propagation gap behind issue #50769.
      +    $candidates = @($PythonVersion) + $PythonFallbackVersions
      +    $seen = @{}
      +    foreach ($ver in $candidates) {
      +        if (-not $ver -or $seen.ContainsKey($ver)) { continue }
      +        $seen[$ver] = $true
      +        try {
      +            $found = & $UvCmd python find $ver 2>$null
      +            if ($found) { return $ver }
      +        } catch { }
      +    }
      +    return $null
      +}
      +
       function Test-Python {
           Write-Info "Checking Python $PythonVersion..."
           
      @@ -566,7 +596,7 @@ function Test-Python {
       
           # Fallback: check if ANY Python 3.10+ is already available on the system
           Write-Info "Trying to find any existing Python 3.10+..."
      -    foreach ($fallbackVer in @("3.12", "3.13", "3.10")) {
      +    foreach ($fallbackVer in $PythonFallbackVersions) {
               try {
                   $pythonPath = & $UvCmd python find $fallbackVer 2>$null
                   if ($pythonPath) {
      @@ -1513,7 +1543,19 @@ function Install-Venv {
               Write-Info "Skipping virtual environment (-NoVenv)"
               return
           }
      -    
      +
      +    # Re-resolve the interpreter before creating the venv.  Under Hermes-Setup.exe
      +    # each stage runs in its own powershell.exe, so the fallback the `python`
      +    # stage picked (e.g. 3.12 when 3.11 is absent) did NOT propagate into this
      +    # fresh process -- $PythonVersion is back at its "3.11" default.  Trusting it
      +    # here made `uv venv venv --python 3.11` fail with exit 2 on machines without
      +    # 3.11 even though the `python` stage reported success (issue #50769).
      +    $resolved = Resolve-AvailablePythonVersion
      +    if ($resolved -and $resolved -ne $PythonVersion) {
      +        Write-Info "Python $PythonVersion not available; using detected Python $resolved"
      +        $script:PythonVersion = $resolved
      +    }
      +
           Write-Info "Creating virtual environment with Python $PythonVersion..."
           
           Push-Location $InstallDir
    • tests/test_install_ps1_python_fallback_venv.py
      @@ -0,0 +1,113 @@
      +"""Regression: the Windows installer must honor its Python fallback at venv time.
      +
      +A user on Windows 11 reported (#50769) that the installer correctly detected a
      +Python 3.12 fallback when 3.11 was absent::
      +
      +    [OK] Found fallback: Python 3.12.8
      +    ...
      +    -> Creating virtual environment with Python 3.11...
      +
      +and then failed with ``Failed to create virtual environment (uv venv exited
      +with 2)``.
      +
      +Root cause: ``Test-Python`` records the fallback via an in-memory
      +``$script:PythonVersion = $fallbackVer`` mutation, but under Hermes-Setup.exe
      +each ``-Stage NAME`` runs in its *own* fresh ``powershell.exe`` process.  The
      +``venv`` stage therefore starts with ``$PythonVersion`` back at its ``"3.11"``
      +default, so ``uv venv venv --python 3.11`` runs on a machine that has no 3.11.
      +
      +The fix re-resolves the interpreter inside ``Install-Venv`` (via the
      +cross-process-safe ``Resolve-AvailablePythonVersion`` helper) before creating
      +the venv, so the venv stage uses whatever interpreter is actually present.
      +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).
      +"""
      +
      +import re
      +from pathlib import Path
      +
      +import pytest
      +
      +_INSTALL_PS1 = Path(__file__).resolve().parents[1] / "scripts" / "install.ps1"
      +
      +
      [redacted:email](scope="module")
      +def source() -> str:
      +    return _INSTALL_PS1.read_text(encoding="utf-8")
      +
      +
      +def _function_body(source: str, name: str) -> str:
      +    """Return the text of a PowerShell ``function <name> { ... }`` block."""
      +    start = source.index(f"function {name}")
      +    brace = source.index("{", start)
      +    depth = 0
      +    for i in range(brace, len(source)):
      +        if source[i] == "{":
      +            depth += 1
      +        elif source[i] == "}":
      +            depth -= 1
      +            if depth == 0:
      +                return source[brace : i + 1]
      +    raise AssertionError(f"unterminated function body for {name}")
      +
      +
      +def test_resolver_helper_is_defined(source: str):
      +    """A cross-process-safe Python-version resolver must exist."""
      +    assert "function Resolve-AvailablePythonVersion" in source, (
      +        "expected a Resolve-AvailablePythonVersion helper that re-resolves the "
      +        "interpreter independently of the in-memory $script:PythonVersion mutation"
      +    )
      +
      +
      +def test_install_venv_reresolves_before_creating_venv(source: str):
      +    """Install-Venv must re-resolve the interpreter BEFORE `uv venv`.
      +
      +    Otherwise the venv stage's fresh process trusts the stale "3.11" default
      +    and fails on machines where only the fallback (e.g. 3.12) is installed.
      +    """
      +    body = _function_body(source, "Install-Venv")
      +    resolve_at = body.find("Resolve-AvailablePythonVersion")
      +    assert resolve_at != -1, (
      +        "Install-Venv must call Resolve-AvailablePythonVersion so the venv "
      +        "stage doesn't trust the stale $PythonVersion default across processes"
      +    )
      +    create_at = body.find("Creating virtual environment with Python")
      +    assert create_at != -1, "expected the venv-creation log line in Install-Venv"
      +    assert resolve_at < create_at, (
      +        "the interpreter must be re-resolved BEFORE the 'Creating virtual "
      +        "environment' step (and before `uv venv` runs)"
      +    )
      +
      +
      +def test_fallback_list_is_single_source_of_truth(source: str):
      +    """The fallback versions live in one shared constant, used by both paths.
      +
      +    A drifting second copy of the list is how detection and venv creation
      +    disagree in the first place.
      +    """
      +    assert re.search(r"\$PythonFallbackVersions\s*=", source), (
      +        "expected a shared $PythonFallbackVersions constant"
      +    )
      +    # Test-Python's fallback loop must iterate the shared constant, not an
      +    # inline literal list.
      +    test_python = _function_body(source, "Test-Python")
      +    assert "foreach ($fallbackVer in $PythonFallbackVersions)" in test_python, (
      +        "Test-Python must iterate the shared $PythonFallbackVersions constant"
      +    )
      +    # The resolver must seed its candidate list from the same constant.
      +    resolver = _function_body(source, "Resolve-AvailablePythonVersion")
      +    assert "$PythonFallbackVersions" in resolver, (
      +        "Resolve-AvailablePythonVersion must reuse the shared fallback constant"
      +    )
      +
      +
      +def test_resolver_prefers_requested_version_then_fallbacks(source: str):
      +    """The resolver tries the requested version first, then the fallbacks."""
      +    resolver = _function_body(source, "Resolve-AvailablePythonVersion")
      +    assert "@($PythonVersion) + $PythonFallbackVersions" in resolver, (
      +        "resolver candidate order must be: requested $PythonVersion first, "
      +        "then the shared fallbacks"
      +    )
      +    assert "uv" in resolver and "python find" in resolver, (
      +        "resolver must probe availability via `uv python find`"
      +    )

4 PRs closed without merging

From the close comments: done elsewhere 1 · duplicate 1 · merged via another change 1 · stale or abandoned 1

  • #24543 fix(install): skip Microsoft Store python.exe stub in Test-Python fallback (#24424) · done elsewhere → 622c27e, #26586
  • #50790 fix(installer): re-resolve Python fallback at venv stage on Windows (#50769) · merged via another change → 23683c3, 34bd6a0, #51602
  • #50840 fix(installer): use detected Python fallback for venv creation · stale or abandoned
  • #50850 fix(installer): use detected Python fallback for venv creation · duplicate → #50790
Closed issues · 2
  • Closed#24424[Bug]: Windows native installation fails if python not installed
  • Closed#50769[Bug]: Installer detects Python 3.12 fallback but still uses 3.11 for venv creation (fails)
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.23: the share of item 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.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
hermes cli
Merged PRs
#26586, #51602
Closed issues
2
Attempts
4 PRs closed without merging
Fix link checked
24 Jun 2026

For your agent

The same facts, typed.