How the Bundled‑Node Launcher Solves the nvm/PATH Problem for Agent Hooks
The bundled‑node launcher eliminates nvm/PATH problems by forcing the Electron binary to execute agent hooks via absolute paths, completely bypassing system PATH dependencies.
Munder‑Difflin executes agent hooks through a specialized bundled‑node launcher that guarantees a working Node runtime regardless of how the user installed Node. When nvm (Node Version Manager) places the node executable only in interactive shell PATHs, standard hook invocations fail with exit 127. The launcher solves this by wrapping the application’s own Electron binary to execute hook scripts, ensuring reliable agent communication even in minimal shell environments.
The nvm/PATH Problem in Hook Execution
When users install Node via nvm, the binary is typically added to PATH only within interactive login shells. However, Munder‑Difflin’s hive spawns hook processes using /bin/sh -c with a minimal environment (/usr/bin:/bin:/usr/sbin:/sbin). In this context, the node command is unavailable, causing hooks to fail immediately with exit 127. This prevents agents from reporting status or receiving stop signals, effectively breaking the agent lifecycle.
How the Bundled‑Node Launcher Works
The solution resides in src/main/hive.ts, which generates a platform‑specific launcher script at <hiveRoot>/bin/hive-node (or hive-node.cmd on Windows). This wrapper forces the Electron runtime (the app’s bundled Node) to execute hook code with an absolute path, rendering the system PATH irrelevant.
Launcher Generation via writeNodeLauncher()
During each bootstrap, the writeNodeLauncher() function creates or updates the launcher script. This ensures the launcher stays current after application upgrades.
// src/main/hive.ts – writeNodeLauncher()
if (process.platform === 'win32') {
writeFileSync(p,
`@echo off\r\nset ELECTRON_RUN_AS_NODE=1\r\n"${process.execPath}" %*\r\n`,
'utf8');
} else {
writeFileSync(p,
`#!/bin/sh\nELECTRON_RUN_AS_NODE=1 exec "${process.execPath}" "$@"\n`,
'utf8');
chmodSync(p, 0o755);
}
The script sets ELECTRON_RUN_AS_NODE=1 to force Electron to run as a Node.js process, then executes the absolute path stored in process.execPath.
Hook Command Construction via nodeRun()
When building command strings for agent hooks, the nodeRun() method substitutes the launcher for a bare node command:
// src/main/hive.ts – nodeRun()
const launcher = this.nodeLauncher(); // absolute path or null
return [launcher ? `"${launcher}"` : 'node',
`"${script}"`, ...args].join(' ');
If the launcher exists, the method returns an absolute command like "/path/to/hive/bin/hive-node" "agents/1234/hook.cjs" …. Otherwise, it falls back to the system node for backward compatibility.
Cross‑Platform Path Resolution
The nodeLauncherPath() and nodeLauncher() methods determine the absolute path to the launcher. By using absolute paths, the system avoids Windows‑specific expansion issues where $HIVE_NODE variables fail in cmd.exe or PowerShell. This ensures consistent behavior across POSIX and Windows systems without shell‑specific quirks.
Practical Implementation Example
When instantiating a Hive and generating a hook command:
const hive = new Hive();
const hookCmd = hive.nodeRun(
join(agentDir, 'my-hook.cjs'),
'--option', 'value'
);
// Output: "/abs/path/to/hive/bin/hive-node" "…/my-hook.cjs" "--option" "value"
The generated POSIX launcher looks like:
#!/bin/sh
ELECTRON_RUN_AS_NODE=1 exec "/Applications/Munder‑Difflin.app/Contents/MacOS/Electron" "$@"
On Windows:
@echo off
set ELECTRON_RUN_AS_NODE=1
"C:\Program Files\Munder‑Difflin\Electron.exe" %*
Summary
- The bundled‑node launcher guarantees executable Node access by wrapping the Electron binary at
src/main/hive.ts. - It bypasses nvm shell‑only PATH restrictions that cause exit 127 failures in minimal
/bin/shenvironments. - Absolute paths eliminate cross‑platform expansion issues on Windows and POSIX systems.
- Automatic regeneration during bootstrap ensures the launcher stays synchronized with application updates.
- The
nodeRun()method insrc/main/hive.tstransparently selects the launcher over system Node when available.
Frequently Asked Questions
Why does nvm cause hook failures if Node is installed?
nvm modifies PATH only in interactive login shells. Hook processes spawned via /bin/sh -c receive a minimal PATH (/usr/bin:/bin:/usr/sbin:/sbin) that excludes nvm’s Node directory, resulting in "command not found" (exit 127) errors when hooks attempt to invoke node.
Where is the bundled-node launcher stored?
The launcher is written to <hiveRoot>/bin/hive-node on POSIX systems or hive-node.cmd on Windows, located within the hive’s installation directory. This path is calculated by nodeLauncherPath() in src/main/hive.ts.
How does the launcher use Electron instead of system Node?
The wrapper script sets ELECTRON_RUN_AS_NODE=1 and invokes process.execPath (the absolute path to the Electron binary). This forces Electron to run in Node.js mode, providing a stable runtime independent of system Node installations or nvm shims.
What happens if the launcher file is missing?
If nodeLauncher() returns null (file does not exist), nodeRun() falls back to the bare node command, preserving backward compatibility while risking PATH-related failures on nvm-managed systems. The launcher regenerates at each bootstrap to prevent this scenario.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →