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.
This commit is contained in:
Siddharth Balyan 2026-04-18 06:51:28 -07:00 committed by GitHub
parent 2edebedc9e
commit 6fb69229ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 56 additions and 10 deletions

View file

@ -897,6 +897,10 @@ def _make_tui_argv(tui_dir: Path, tui_dev: bool) -> tuple[list[str], Path]:
_ensure_tui_node() _ensure_tui_node()
def _node_bin(bin: str) -> str: 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) path = shutil.which(bin)
if not path: if not path:
print(f"{bin} not found — install Node.js to use the TUI.") print(f"{bin} not found — install Node.js to use the TUI.")

View file

@ -125,6 +125,29 @@ json.dump(sorted(leaf_paths(DEFAULT_CONFIG)), sys.stdout, indent=2)
echo "ok" > $out/result 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 # Verify HERMES_MANAGED guard works on all mutation commands
managed-guard = pkgs.runCommand "hermes-managed-guard" { } '' managed-guard = pkgs.runCommand "hermes-managed-guard" { } ''
set -e set -e

View file

@ -121,11 +121,19 @@
# ── Provision apt packages (first boot only, cached in writable layer) ── # ── Provision apt packages (first boot only, cached in writable layer) ──
# sudo: agent self-modification # sudo: agent self-modification
# nodejs/npm: writable node so npm i -g works (nix store copies are read-only) # 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 if [ ! -f /var/lib/hermes-tools-provisioned ] && command -v apt-get >/dev/null 2>&1; then
echo "First boot: provisioning agent tools..." echo "First boot: provisioning agent tools..."
apt-get update -qq 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 touch /var/lib/hermes-tools-provisioned
fi fi
@ -171,7 +179,7 @@
# Package and entrypoint use stable symlinks (current-package, current-entrypoint) # Package and entrypoint use stable symlinks (current-package, current-entrypoint)
# so they can update without recreation. Env vars go through $HERMES_HOME/.env. # so they can update without recreation. Env vars go through $HERMES_HOME/.env.
containerIdentity = builtins.hashString "sha256" (builtins.toJSON { 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; image = cfg.container.image;
extraVolumes = cfg.container.extraVolumes; extraVolumes = cfg.container.extraVolumes;
extraOptions = cfg.container.extraOptions; extraOptions = cfg.container.extraOptions;

View file

@ -63,7 +63,8 @@
--suffix PATH : "${runtimePath}" \ --suffix PATH : "${runtimePath}" \
--set HERMES_BUNDLED_SKILLS $out/share/hermes-agent/skills \ --set HERMES_BUNDLED_SKILLS $out/share/hermes-agent/skills \
--set HERMES_TUI_DIR $out/ui-tui \ --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" "hermes"

View file

@ -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: pythonPackageOverrides = final: _prev:
if isAarch64Darwin then { if isAarch64Darwin then {
numpy = mkPrebuiltOverride final python311.pkgs.numpy { }; numpy = mkPrebuiltOverride final python311.pkgs.numpy { };
@ -75,6 +89,7 @@ let
(lib.composeManyExtensions [ (lib.composeManyExtensions [
pyproject-build-systems.overlays.default pyproject-build-systems.overlays.default
overlay overlay
buildSystemOverrides
pythonPackageOverrides pythonPackageOverrides
]); ]);
in in

View file

@ -4,7 +4,7 @@ let
src = ../ui-tui; src = ../ui-tui;
npmDeps = pkgs.fetchNpmDeps { npmDeps = pkgs.fetchNpmDeps {
inherit src; inherit src;
hash = "sha256-zsUPmbC6oMUO10EhS3ptvDjwlfpCSEmrkjyeORw7fac="; hash = "sha256-mG3vpgGi4ljt4X3XIf3I/5mIcm+rVTUAmx2DQ6YVA90=";
}; };
packageJson = builtins.fromJSON (builtins.readFile (src + "/package.json")); packageJson = builtins.fromJSON (builtins.readFile (src + "/package.json"));
@ -18,11 +18,6 @@ pkgs.buildNpmPackage {
doCheck = false; doCheck = false;
postPatch = ''
# fetchNpmDeps strips the trailing newline; match it so the diff passes
sed -i -z 's/\n$//' package-lock.json
'';
installPhase = '' installPhase = ''
runHook preInstall runHook preInstall