ci(e2e): provision node through PM so /api/pty can start the TUI

The dashboard PTY test failed on this branch with "node: not installed and
lazy installs are disabled". On this branch _make_tui_argv asks PM for node
(hermes_cli/main_tui_launch.py _tui_node_bin). The E2E sandbox forbids lazy
installs (HERMES_DISABLE_LAZY_INSTALLS=1 in tests/e2e/core/dashboard
hermetic_env). It gets node only when the runner's PM store has it
(tests/e2e/core/_pm_dependencies.py). The e2e job got node from
actions/setup-node, so node was on PATH but not in the PM store. main passes
the test because main's TUI launch takes node from PATH.

Install the locked toolchain (setup-pm toolchain: all) before npm ci, in
place of setup-node. The locked Node 26 also satisfies the boot-contract
suite. When HERMES_E2E_REQUIRE_TUI=1, the fixture now fails at once if the
store has no node, not later with an opaque 1011 close.
This commit is contained in:
ethernet
2026-09-24 16:45:18 -04:00
parent 1fc6bc8cee
commit fcd4e40dfc
2 changed files with 21 additions and 12 deletions

View File

@@ -132,12 +132,20 @@ jobs:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Node
# The boot-contract suite feeds serve's stdout through Desktop's own
# backend-ready.ts (native TS stripping needs Node >= 22.18).
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
- name: Set up locked Python, Node and test dependencies
# toolchain: all puts PM's locked Node in the runner's PM store, not
# only on PATH. The E2E sandboxes copy node from that store
# (tests/e2e/core/_pm_dependencies.py) and forbid lazy installs, and
# the dashboard's /api/pty resolves the TUI's node through PM only.
# A setup-node binary on PATH is invisible to PM, so every /api/pty
# chat refused to start. The locked Node also satisfies the
# boot-contract suite (backend-ready.ts needs Node >= 22.18).
uses: ./.github/actions/setup-pm
with:
node-version: 26
toolchain: all
extras: '["all", "anthropic", "bedrock", "mistral", "fal", "modal", "daytona", "hindsight", "parallel-web"]'
test-environment: 'true'
prune-python-cache: true
- name: Install Node deps and build the Ink TUI
# tests/e2e/core/terminal drives the real `hermes --tui` over a PTY;
@@ -160,13 +168,6 @@ jobs:
rm -rf "$RG_TARBALL" "ripgrep-${RG_VERSION}-x86_64-unknown-linux-musl"
rg --version
- name: Set up locked Python and test dependencies
uses: ./.github/actions/setup-pm
with:
extras: '["all", "anthropic", "bedrock", "mistral", "fal", "modal", "daytona", "hindsight", "parallel-web"]'
test-environment: 'true'
prune-python-cache: true
- name: Require a WAL-capable SQLite
# The state.db suites skip on a WAL-reset-vulnerable SQLite; fail
# instead of reporting green over zero coverage.

View File

@@ -4,6 +4,7 @@ from __future__ import annotations
import atexit
import json
import os
import shutil
import sys
import tempfile
@@ -28,6 +29,13 @@ def _prepare_test_tools() -> tuple[Path, dict] | None:
# node runs the TUI behind the dashboard's /api/pty; carry it when the
# runner's store has it, or PM in the sandbox reports it not installed.
names = ("uv", "python") + (("node",) if facts.get("node") else ())
if "node" not in names and os.environ.get("HERMES_E2E_REQUIRE_TUI") == "1":
# A node on PATH alone (actions/setup-node) is invisible to PM, so
# every /api/pty chat would fail later with an opaque close 1011.
raise AssertionError(
f"HERMES_E2E_REQUIRE_TUI=1 but the PM store has no node: {source} "
"(install it with setup-pm `toolchain: all`)"
)
records = {name: facts.get(name) for name in names}
python = records["python"]
if not python or (source / python["entry"]).resolve() != Path(sys.base_prefix).resolve():