fix(install): guard Windows desktop installs against broken web_server

This commit is contained in:
konsisumer 2026-07-06 00:58:24 +02:00 committed by Teknium
parent 94205a1139
commit 18e840469f
4 changed files with 73 additions and 4 deletions

View file

@ -4443,14 +4443,17 @@ def _clear_bytecode_cache(root: Path) -> int:
return removed
# Critical files that every ``hermes`` invocation imports at startup. If any
# of these fail to parse after a pull, the CLI is bricked — the user can't
# even run ``hermes update`` again to roll forward. The post-pull syntax
# guard validates these and auto-rolls-back on failure.
# Critical files that Hermes must be able to import immediately after an
# update/install. Most are imported on every CLI startup; ``web_server.py``
# is the desktop/dashboard backend path that a fresh Windows install launches
# right away. If any of these fail to parse after a pull, the user can be
# left with a bricked CLI or desktop backend. The post-pull syntax guard
# validates these and auto-rolls-back on failure.
_UPDATE_CRITICAL_FILES = (
"hermes_cli/main.py",
"hermes_cli/config.py",
"hermes_cli/__init__.py",
"hermes_cli/web_server.py",
"cli.py",
"run_agent.py",
"model_tools.py",

View file

@ -1995,6 +1995,7 @@ print(','.join(scripts))
$pythonExe = if (-not $NoVenv) { "$InstallDir\venv\Scripts\python.exe" } else { (& $UvCmd python find $PythonVersion) }
if (Test-Path $pythonExe) {
$webOk = $false
$webServerSyntaxOk = $false
# Relax EAP=Stop while running the import probe; see the matching
# comment on the baseline-imports check above. Python writes
# deprecation warnings to stderr and we don't want those wrapped
@ -2006,6 +2007,10 @@ print(','.join(scripts))
& $pythonExe -c "import fastapi, uvicorn" 2>&1 | Out-Null
if ($LASTEXITCODE -eq 0) { $webOk = $true }
} catch { }
try {
& $pythonExe -m py_compile "$InstallDir\hermes_cli\web_server.py" 2>&1 | Out-Null
if ($LASTEXITCODE -eq 0) { $webServerSyntaxOk = $true }
} catch { }
$ErrorActionPreference = $prevEAP
if (-not $webOk) {
Write-Warn "fastapi/uvicorn not importable -- `hermes dashboard` will not work."
@ -2017,6 +2022,9 @@ print(','.join(scripts))
Write-Warn "Could not install [web] extra. Run manually: uv pip install --python `"$pythonExe`" `"fastapi>=0.104,<1`" `"uvicorn[standard]>=0.24,<1`""
}
}
if (-not $webServerSyntaxOk) {
throw "dashboard backend source failed syntax check: hermes_cli/web_server.py"
}
}
Pop-Location

View file

@ -116,6 +116,15 @@ def test_validate_critical_files_syntax_detects_break_in_main_py(tmp_path):
assert failing_path is not None and failing_path.endswith("hermes_cli/main.py")
def test_validate_critical_files_syntax_detects_break_in_web_server(tmp_path):
_populate_critical_tree(tmp_path, broken_file="hermes_cli/web_server.py")
ok, failing_path, _ = hermes_main._validate_critical_files_syntax(tmp_path)
assert ok is False
assert failing_path is not None and failing_path.endswith("hermes_cli/web_server.py")
def test_validate_critical_files_syntax_tolerates_missing_files(tmp_path):
"""A refactor may legitimately remove one of the critical files — the
guard should skip missing files, not falsely flag the install as broken."""

View file

@ -0,0 +1,49 @@
"""Regression: install.ps1 must syntax-check the dashboard backend source.
Issue #59004 reported a fresh Windows desktop install crashing on launch
because ``hermes_cli/web_server.py`` inside the installed checkout still
contained merge-conflict markers. Import-only dependency probes (fastapi /
uvicorn) do not catch that: the packages can be present while the backend
source itself is unparsable.
This test is source-level because Linux CI cannot execute the PowerShell
installer. It locks the contract that install.ps1 runs ``py_compile`` against
``hermes_cli/web_server.py`` and fails the stage when that syntax probe fails.
"""
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 test_install_ps1_compiles_web_server_source_after_web_deps_probe() -> None:
text = INSTALL_PS1.read_text(encoding="utf-8")
probe = re.search(
r'import fastapi, uvicorn[\s\S]{0,1200}?-m py_compile "\$InstallDir\\hermes_cli\\web_server\.py"',
text,
)
assert probe is not None, (
"install.ps1 must syntax-check hermes_cli/web_server.py after the "
"dashboard dependency probe so a fresh desktop install fails early on "
"merge-conflict markers or other SyntaxErrors."
)
def test_install_ps1_fails_stage_when_web_server_syntax_probe_fails() -> None:
text = INSTALL_PS1.read_text(encoding="utf-8")
assert "if ($LASTEXITCODE -eq 0) { $webServerSyntaxOk = $true }" in text
assert "if (-not $webServerSyntaxOk) {" in text
assert (
'throw "dashboard backend source failed syntax check: hermes_cli/web_server.py"'
in text
), (
"install.ps1 must fail the install stage when hermes_cli/web_server.py "
"does not compile, instead of writing a broken desktop/backend install."
)