diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3056d99856..f38565e9a6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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. diff --git a/tests/e2e/core/_pm_dependencies.py b/tests/e2e/core/_pm_dependencies.py index 5c5ea2731d..ed78f2a20a 100644 --- a/tests/e2e/core/_pm_dependencies.py +++ b/tests/e2e/core/_pm_dependencies.py @@ -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():