Files
hermes-agent/tests/hermes_cli/test_startup_fast_guards.py
kshitijk4poor e4257c171a refactor: single chokepoint for the pre-import version fast path
Architecture fix for the bug class behind the Termux --version NameError
(live on main since eb4040242): version-printing kept being reimplemented
as *_fast() copies at the top of hermes_cli/main.py, each duplicating
canonical logic (project-root resolution, container detection, profile
detection). The copies drift silently — eb4040242 edited the canonical
output and referenced the PROJECT_ROOT module constant inside the fast
function, which doesn't exist yet at the fast exit point.

- hermes_cli/_startup_fast.py: THE implementations, stdlib-only. main.py's
  *_fast() names become thin delegates (kept for test/back-compat), and
  PROJECT_ROOT itself derives from the same helper — the constant and the
  fast path can no longer disagree.
- Fast output now includes the .install_method stamp (one cheap file read)
  and a 'Run hermes version for update status' pointer, so globalizing the
  fast path doesn't silently drop slow-path info.
- Guard tests: (1) import-weight — subprocess-imports _startup_fast and
  fails if any heavy module (config/yaml/argparse/cli/run_agent/httpx)
  lands in sys.modules; (2) subprocess parity on+off Termux — the test
  that would have caught eb4040242 the day it landed; (3) install-method
  stamp surfacing.

hermes --version: ~3.8s cold / 0.2-0.4s warm -> 0.01-0.02s everywhere.
2026-08-02 21:13:12 +05:30

107 lines
3.5 KiB
Python

"""Guards for hermes_cli._startup_fast — the pre-import version fast path.
Two invariants, each of which has been broken before:
1. IMPORT WEIGHT: _startup_fast must stay stdlib-only. The whole point of
the module is to run before main.py's heavy import wall; one careless
``from hermes_cli.config import ...`` silently makes `hermes --version`
slow again for everyone (the regression would be invisible — everything
still works, just 40x slower).
2. OUTPUT PARITY / LIVENESS: the fast path must actually produce version
output and exit 0 in a real subprocess, on and off Termux. This is the
test that would have caught eb4040242, which changed the canonical
version output to reference the PROJECT_ROOT module constant inside the
fast function — a name that doesn't exist yet at the fast exit point —
NameError-ing the Termux fast path in production for weeks.
"""
import json
import os
import subprocess
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
# Modules that must NEVER be imported by the fast path. Each one either
# pulls yaml/argparse/logging config or is itself a god-module.
_FORBIDDEN_MODULES = (
"hermes_cli.config",
"hermes_cli.main",
"yaml",
"argparse",
"cli",
"run_agent",
"model_tools",
"httpx",
"openai",
)
def test_startup_fast_import_weight():
"""Importing _startup_fast must not drag in any heavy module."""
probe = (
"import sys, json\n"
"import hermes_cli._startup_fast\n"
"print(json.dumps(sorted(sys.modules.keys())))\n"
)
result = subprocess.run(
[sys.executable, "-c", probe],
capture_output=True,
text=True,
timeout=30,
cwd=REPO_ROOT,
)
assert result.returncode == 0, result.stderr
loaded = set(json.loads(result.stdout))
offenders = [m for m in _FORBIDDEN_MODULES if m in loaded]
assert not offenders, (
f"hermes_cli._startup_fast imported heavy modules: {offenders} — "
"the fast path must stay stdlib-only (see module docstring)."
)
def _run_version(env_overrides: dict) -> subprocess.CompletedProcess:
env = {**os.environ, **env_overrides}
env.pop("HERMES_DEV", None)
return subprocess.run(
[sys.executable, "-m", "hermes_cli.main", "--version"],
capture_output=True,
text=True,
timeout=60,
cwd=REPO_ROOT,
env=env,
)
def test_fast_version_parity_off_termux(tmp_path):
home = tmp_path / ".hermes"
home.mkdir()
result = _run_version({"HERMES_HOME": str(home), "TERMUX_VERSION": ""})
assert result.returncode == 0, result.stderr
out = result.stdout
for field in ("Hermes Agent v", "Install directory:", "Python:", "OpenAI SDK:"):
assert field in out, f"fast --version output missing {field!r}:\n{out}"
def test_fast_version_parity_on_termux(tmp_path):
"""The historical Termux path — the one eb4040242 broke."""
home = tmp_path / ".hermes"
home.mkdir()
result = _run_version(
{"HERMES_HOME": str(home), "TERMUX_VERSION": "0.118"}
)
assert result.returncode == 0, result.stderr
assert "Hermes Agent v" in result.stdout
assert "Traceback" not in result.stderr
def test_fast_version_reports_install_method_stamp(tmp_path):
home = tmp_path / ".hermes"
home.mkdir()
(home / ".install_method").write_text("git\n", encoding="utf-8")
result = _run_version({"HERMES_HOME": str(home), "TERMUX_VERSION": ""})
assert result.returncode == 0, result.stderr
assert "Install method: git" in result.stdout