Skip to content

fix(desktop): provision Node before snapshotting build env in cmd_gui - #55288

Open
jecanore wants to merge 2 commits into
NousResearch:mainfrom
jecanore:fix/desktop-rebuild-provision-node
Open

fix(desktop): provision Node before snapshotting build env in cmd_gui#55288
jecanore wants to merge 2 commits into
NousResearch:mainfrom
jecanore:fix/desktop-rebuild-provision-node

Conversation

@jecanore

Copy link
Copy Markdown

What does this PR do?

Fixes a macOS desktop-update failure where the GUI updater rebuild aborts with
Desktop GUI requires Node.js/npm, but npm was not found on PATH, even though a
terminal can build the app fine.

Root cause. A desktop rebuild launched from the GUI (Finder/launchd) —
including the installer's hermes desktop --build-only update step — inherits
the launchd-default PATH (/usr/bin:/bin:/usr/sbin:/sbin), which omits
version-manager and Homebrew Node (~/.nvm, ~/.fnm, /opt/homebrew/bin).
When no Hermes-managed Node tree exists — the common case after a terminal
install, where ensure_node accepts the user's existing nvm/brew Node and never
provisions $HERMES_HOME/nodecmd_gui's find_node_executable("npm") falls
through to shutil.which("npm") against that stripped PATH and finds nothing.

#49254 made Hermes prefer managed Node when it exists; this is the sibling
case where no managed Node exists at all and the system Node is invisible to a
non-shell launch.

Fix. cmd_gui already imports the node-bootstrap cascade the TUI self-heals
with (_ensure_tui_node(), which sources scripts/lib/node-bootstrap.sh and
runs ensure_node — fnm/proto/nvm/brew/bundled, discovering nvm by sourcing
nvm.sh directly rather than via PATH) but never called it. This calls it,
gated on a build actually running.

Placement is the subtle part: _ensure_tui_node() must run before the build
env is snapshotted via with_hermes_node_path(), not just before npm
resolution. The build runs as subprocess.run([npm, "run", ...], env=env), and
npm's #!/usr/bin/env node shebang resolves node against that env's PATH at
exec time. Repairing PATH only after the snapshot would let npm resolve yet
fail the build with env: node: No such file (exit 127). Repairing
os.environ["PATH"] before the snapshot fixes both at once.

Related Issue

Refs #49242 (the POSIX/macOS sibling of that issue — intentionally not
Fixes, since #49242 also tracks the Windows WhatsApp paths and the #49259
resolver consolidation, which this PR does not touch).

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✅ Tests (adding or improving test coverage)

Changes Made

  • hermes_cli/main.py (cmd_gui): hoist the source_mode/skip_build/
    force_build flags above the env snapshot and call _ensure_tui_node() there
    when a build may run, so node/npm (and the node npm's shebang needs) are on
    PATH for both resolution and the build subprocess. No-op when node+npm are
    already present, or when node-bootstrap.sh/bash are absent (e.g. Windows,
    where managed Node under %LOCALAPPDATA%\hermes\node already covers this).
  • tests/hermes_cli/test_gui_command.py: add test_gui_repairs_path_before_env_snapshot
    (asserts _ensure_tui_node() runs before with_hermes_node_path() — the
    exit-127 ordering guard) and test_gui_skips_path_repair_when_skipping_build.

How to Test

Reproduce the GUI/launchd environment from a terminal (node visible normally, so
the bug needs a stripped PATH and no managed Node):

  1. Have Node via nvm/fnm/Homebrew and no $HERMES_HOME/node tree.
  2. Before the fix, with a stripped PATH:
    PATH=/usr/bin:/bin:/usr/sbin:/sbin HERMES_HOME=/tmp/scratch-home \
      hermes desktop --build-only --force-build
    
    → fails: npm was not found on PATH.
  3. After the fix, the same command provisions/activates Node via
    node-bootstrap.sh and the rebuild proceeds.

Automated + mechanism proof run in this branch:

  • pytest tests/hermes_cli/test_gui_command.py tests/hermes_cli/test_cmd_update.py -q → 90 passed.
  • scripts/check-windows-footguns.py --diff upstream/main → clean.
  • Direct repro of the shebang/env mechanism under a real stripped PATH + real
    nvm + empty HERMES_HOME: with the buggy ordering (snapshot first) npm and
    node both resolve to None; with the fix, npm/node resolve and
    npm --version (the #!/usr/bin/env node path) returns rc 0.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched for existing PRs to make sure this isn't a duplicate (fix(windows): prefer managed node for whatsapp and desktop #49239 closed/superseded, fix(windows): prefer managed node for whatsapp and desktop #49254 merged — neither covers the no-managed-Node POSIX case)
  • My PR contains only changes related to this fix
  • I've run pytest tests/ -q and all tests pass — ran the targeted test_gui_command.py + test_cmd_update.py suites (90 passed) and the footgun check; leaving the full suite to CI
  • I've added tests for my changes
  • I've tested on my platform: macOS 15 (Apple Silicon)

Documentation & Housekeeping

  • Updated relevant documentation — N/A (behavior fix, no public API/config change)
  • Updated cli-config.yaml.example — N/A
  • Updated CONTRIBUTING.md/AGENTS.md — N/A
  • Considered cross-platform impact — yes; no-op on Windows (bash/node-bootstrap.sh absent → _ensure_tui_node() returns early), where managed Node already covers the case
  • Updated tool descriptions/schemas — N/A

Notes for reviewers

_build_web_ui() (the hermes serve dashboard build) has the same
resolve-then-snapshot shape and would benefit from the same treatment, but it's
a different command and not normally launched from a GUI/launchd context — left
out to keep this PR single-concern; happy to follow up if you'd like it folded
in.

@alt-glitch alt-glitch added type/bug Something isn't working comp/cli CLI entry point, hermes_cli/, setup wizard comp/desktop Electron desktop app (apps/desktop/*) P2 Medium — degraded but workaround exists labels Jun 30, 2026

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: LGTM

Desktop fix for Node.js provisioning before snapshotting build env.

Looks Good

  • Calls _ensure_tui_node() before with_hermes_node_path() snapshot
  • Fixes the case where no managed Node exists and system Node is invisible to non-shell launch
  • Well-documented root cause: launchd PATH omits version-manager Node
  • Placement is correct: PATH repair before env snapshot

jecanore and others added 2 commits July 11, 2026 18:22
A desktop rebuild launched from the GUI (Finder/launchd) — including the
installer's `hermes desktop --build-only` update step — inherits a stripped
PATH that omits version-manager and Homebrew Node (~/.nvm, ~/.fnm,
/opt/homebrew/bin). When no Hermes-managed Node exists (the common case after a
terminal install accepts an nvm/brew Node), cmd_gui's `find_node_executable`
falls through to `shutil.which` against that stripped PATH and the rebuild
fails with "npm was not found on PATH", even though a terminal builds fine.

cmd_gui already imports the node-bootstrap cascade the TUI self-heals with
(`_ensure_tui_node`) but never called it. Call it before snapshotting the build
env via `with_hermes_node_path()`, gated on a build actually running. Placement
matters: the build subprocess runs with the env snapshot, and npm's
`#!/usr/bin/env node` shebang fails (exit 127) if node's dir is absent from that
env — so PATH must be repaired before the snapshot, not just before npm
resolution.

Refs NousResearch#49242.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jecanore
jecanore force-pushed the fix/desktop-rebuild-provision-node branch from 7c01948 to a304239 Compare July 11, 2026 23:25
@jecanore

Copy link
Copy Markdown
Author

Rebased onto current main (4281151ae) and added an integration-style macOS regression test that starts with a Finder/launchd-style restricted PATH, exercises the real _ensure_tui_node() recovery, and verifies the recovered Node directory is present in the desktop build environment.

Current verification:

  • python3 -m pytest tests/hermes_cli/test_gui_command.py tests/hermes_cli/test_cmd_update.py -q -> 91 passed
  • python3 -m py_compile hermes_cli/main.py tests/hermes_cli/test_gui_command.py -> passed
  • python3 scripts/check-windows-footguns.py --diff origin/main -> clean
  • git diff --check -> clean

Requesting maintainer review from @OutThisLife because this follows the macOS desktop self-update path addressed in #38296.

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused regression fix. Current main still takes the build-environment snapshot in hermes_cli/main.py:5642 before resolving npm at hermes_cli/main.py:5669; absent a managed Node tree, find_node_executable() falls back to the stripped process PATH (hermes_constants.py:505-518). The proposed call to _ensure_tui_node() before that snapshot directly restores the missing path propagation for npm's Node shebang.

The update path is real: apps/desktop/electron/main.ts:2861 runs hermes desktop --build-only, while runStreamedUpdate() preserves process.env at apps/desktop/electron/main.ts:2727, allowing the helper's nvm/fnm/Homebrew discovery. The regression tests cover both ordering and a restricted-PATH build environment. GitHub reports the branch is two commits ahead of base 4281151ae and not behind, so salvage should be a clean cherry-pick.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/desktop Electron desktop app (apps/desktop/*) P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants