Skip to content

Commit 1a86cf8

Browse files
acedatacloud-devGermey
authored andcommitted
fix: resolve shell PATH in Electron to fix 'spawn node ENOENT'
Electron apps launched from Dock/Finder inherit a minimal PATH (/usr/bin:/bin:/usr/sbin:/sbin) that doesn't include user-installed node (nvm, volta, homebrew, fnm, etc.). This causes the Claude Agent SDK's child_process.spawn('node', ...) to fail with ENOENT. Add fixElectronPath() that runs the user's login shell to resolve their full PATH before the app starts. Falls back to probing common node binary directories if shell resolution fails.
1 parent dc2211c commit 1a86cf8

1 file changed

Lines changed: 39 additions & 0 deletions

File tree

electron/main.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
Notification,
1818
shell
1919
} from 'electron';
20+
import { execFileSync } from 'node:child_process';
2021
import { existsSync, readFileSync, readdirSync } from 'node:fs';
2122
import { join, dirname } from 'path';
2223
import { fileURLToPath } from 'url';
@@ -136,6 +137,44 @@ function pushRecentMessage(entry: {
136137
}
137138
}
138139

140+
// ---------------------------------------------------------------------------
141+
// Fix PATH for macOS/Linux — Electron GUI apps inherit a minimal PATH that
142+
// doesn't include user-installed node (nvm, volta, homebrew, fnm, etc.).
143+
// We resolve the real PATH from the user's login shell before anything runs.
144+
// ---------------------------------------------------------------------------
145+
146+
function fixElectronPath(): void {
147+
if (process.platform !== 'darwin' && process.platform !== 'linux') return;
148+
149+
try {
150+
const shell = process.env.SHELL || '/bin/zsh';
151+
const result = execFileSync(shell, ['-ilc', 'echo -n "$PATH"'], {
152+
encoding: 'utf8',
153+
timeout: 5000,
154+
stdio: ['ignore', 'pipe', 'ignore']
155+
}).trim();
156+
if (result) {
157+
process.env.PATH = result;
158+
logger.info('Resolved shell PATH for Electron', { pathLength: result.length });
159+
}
160+
} catch {
161+
// Fallback: prepend common node binary directories
162+
const home = homedir();
163+
const fallbackPaths = [
164+
'/opt/homebrew/bin',
165+
'/usr/local/bin',
166+
`${home}/.volta/bin`,
167+
`${home}/.fnm/aliases/default/bin`
168+
].filter((p) => existsSync(p));
169+
if (fallbackPaths.length) {
170+
process.env.PATH = [...fallbackPaths, process.env.PATH].join(':');
171+
logger.info('Applied fallback PATH entries', { added: fallbackPaths });
172+
}
173+
}
174+
}
175+
176+
fixElectronPath();
177+
139178
// ---------------------------------------------------------------------------
140179
// Single instance lock
141180
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)