From 6fb69229caba4bd5699228e520de4956b3458187 Mon Sep 17 00:00:00 2001 From: Siddharth Balyan <52913345+alt-glitch@users.noreply.github.com> Date: Sat, 18 Apr 2026 06:51:28 -0700 Subject: [PATCH] fix(nix): fix build failures, TUI Node.js crash, and upgrade container to Node 22 (#12159) * Add setuptools build dep for legacy alibabacloud packages and updated stale npm-deps hash * Add HERMES_NODE env var to pin Node.js version The TUI requires Node.js 20+ for regex `/v` flag support (used by string-width). Instead of relying on PATH lookup, explicitly set HERMES_NODE to the bundled Node 22 in the Nix wrapper, and add a fallback check in the Python code to use HERMES_NODE if available. Also upgrade container provisioning to Node 22 via NodeSource (Ubuntu 24.04 ships Node 18 which is EOL) and add a Nix check to verify the wrapper and Node version at build time. --- hermes_cli/main.py | 4 ++++ nix/checks.nix | 23 +++++++++++++++++++++++ nix/nixosModules.nix | 14 +++++++++++--- nix/packages.nix | 3 ++- nix/python.nix | 15 +++++++++++++++ nix/tui.nix | 7 +------ 6 files changed, 56 insertions(+), 10 deletions(-) diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 0afadac3d1..a13a6f88ee 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -897,6 +897,10 @@ def _make_tui_argv(tui_dir: Path, tui_dev: bool) -> tuple[list[str], Path]: _ensure_tui_node() def _node_bin(bin: str) -> str: + if bin == "node": + env_node = os.environ.get("HERMES_NODE") + if env_node and os.path.isfile(env_node) and os.access(env_node, os.X_OK): + return env_node path = shutil.which(bin) if not path: print(f"{bin} not found — install Node.js to use the TUI.") diff --git a/nix/checks.nix b/nix/checks.nix index 55068a94f1..ff8e7947c5 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -125,6 +125,29 @@ json.dump(sorted(leaf_paths(DEFAULT_CONFIG)), sys.stdout, indent=2) echo "ok" > $out/result ''; + # Verify HERMES_NODE is set in wrapper and points to Node 20+ + # (string-width uses the /v regex flag which requires Node 20+) + hermes-node = pkgs.runCommand "hermes-node-version" { } '' + set -e + echo "=== Checking HERMES_NODE in wrapper ===" + grep -q "HERMES_NODE" ${hermes-agent}/bin/hermes || \ + (echo "FAIL: HERMES_NODE not set in wrapper"; exit 1) + echo "PASS: HERMES_NODE present in wrapper" + + HERMES_NODE=$(sed -n "s/^export HERMES_NODE='\(.*\)'/\1/p" ${hermes-agent}/bin/hermes) + test -x "$HERMES_NODE" || (echo "FAIL: HERMES_NODE=$HERMES_NODE not executable"; exit 1) + echo "PASS: HERMES_NODE executable at $HERMES_NODE" + + NODE_MAJOR=$("$HERMES_NODE" --version | sed 's/^v//' | cut -d. -f1) + test "$NODE_MAJOR" -ge 20 || \ + (echo "FAIL: Node v$NODE_MAJOR < 20, TUI needs /v regex flag support"; exit 1) + echo "PASS: Node v$NODE_MAJOR >= 20" + + echo "=== All HERMES_NODE checks passed ===" + mkdir -p $out + echo "ok" > $out/result + ''; + # Verify HERMES_MANAGED guard works on all mutation commands managed-guard = pkgs.runCommand "hermes-managed-guard" { } '' set -e diff --git a/nix/nixosModules.nix b/nix/nixosModules.nix index 75b3dca31b..24a2a1b6dd 100644 --- a/nix/nixosModules.nix +++ b/nix/nixosModules.nix @@ -121,11 +121,19 @@ # ── Provision apt packages (first boot only, cached in writable layer) ── # sudo: agent self-modification # nodejs/npm: writable node so npm i -g works (nix store copies are read-only) - # curl: needed for uv installer + # Node 22 via NodeSource — Ubuntu 24.04 ships Node 18 which is EOL. + # curl: needed for uv installer + NodeSource setup if [ ! -f /var/lib/hermes-tools-provisioned ] && command -v apt-get >/dev/null 2>&1; then echo "First boot: provisioning agent tools..." apt-get update -qq - apt-get install -y -qq sudo nodejs npm curl + apt-get install -y -qq sudo curl ca-certificates gnupg + mkdir -p /etc/apt/keyrings + curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \ + | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg + echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" \ + > /etc/apt/sources.list.d/nodesource.list + apt-get update -qq + apt-get install -y -qq nodejs touch /var/lib/hermes-tools-provisioned fi @@ -171,7 +179,7 @@ # Package and entrypoint use stable symlinks (current-package, current-entrypoint) # so they can update without recreation. Env vars go through $HERMES_HOME/.env. containerIdentity = builtins.hashString "sha256" (builtins.toJSON { - schema = 3; # bump when identity inputs change + schema = 4; # bump when identity inputs change (4: Node 18→22 via NodeSource) image = cfg.container.image; extraVolumes = cfg.container.extraVolumes; extraOptions = cfg.container.extraOptions; diff --git a/nix/packages.nix b/nix/packages.nix index f39d9d0b2b..968ad12fb7 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -63,7 +63,8 @@ --suffix PATH : "${runtimePath}" \ --set HERMES_BUNDLED_SKILLS $out/share/hermes-agent/skills \ --set HERMES_TUI_DIR $out/ui-tui \ - --set HERMES_PYTHON ${hermesVenv}/bin/python3 + --set HERMES_PYTHON ${hermesVenv}/bin/python3 \ + --set HERMES_NODE ${pkgs.nodejs_22}/bin/node '') [ "hermes" diff --git a/nix/python.nix b/nix/python.nix index 160b4ee790..91411f4d75 100644 --- a/nix/python.nix +++ b/nix/python.nix @@ -35,6 +35,20 @@ let }; }; + # Legacy alibabacloud packages ship only sdists with setup.py/setup.cfg + # and no pyproject.toml, so setuptools isn't declared as a build dep. + buildSystemOverrides = final: prev: builtins.mapAttrs + (name: _: prev.${name}.overrideAttrs (old: { + nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ final.setuptools ]; + })) + (lib.genAttrs [ + "alibabacloud-credentials-api" + "alibabacloud-endpoint-util" + "alibabacloud-gateway-dingtalk" + "alibabacloud-gateway-spi" + "alibabacloud-tea" + ] (_: null)); + pythonPackageOverrides = final: _prev: if isAarch64Darwin then { numpy = mkPrebuiltOverride final python311.pkgs.numpy { }; @@ -75,6 +89,7 @@ let (lib.composeManyExtensions [ pyproject-build-systems.overlays.default overlay + buildSystemOverrides pythonPackageOverrides ]); in diff --git a/nix/tui.nix b/nix/tui.nix index 70eb67f949..7303edecb9 100644 --- a/nix/tui.nix +++ b/nix/tui.nix @@ -4,7 +4,7 @@ let src = ../ui-tui; npmDeps = pkgs.fetchNpmDeps { inherit src; - hash = "sha256-zsUPmbC6oMUO10EhS3ptvDjwlfpCSEmrkjyeORw7fac="; + hash = "sha256-mG3vpgGi4ljt4X3XIf3I/5mIcm+rVTUAmx2DQ6YVA90="; }; packageJson = builtins.fromJSON (builtins.readFile (src + "/package.json")); @@ -18,11 +18,6 @@ pkgs.buildNpmPackage { doCheck = false; - postPatch = '' - # fetchNpmDeps strips the trailing newline; match it so the diff passes - sed -i -z 's/\n$//' package-lock.json - ''; - installPhase = '' runHook preInstall