Skip to content

Commit 89e52e0

Browse files
authored
🤖 fix: resolve editor commands using shell PATH (#1073)
_Generated with `mux`_ When Electron spawns child processes without `shell: true`, it uses a minimal PATH that doesn't include directories like `/opt/homebrew/bin`. This caused 'Editor command not found' errors for commands like `code` even when they were available in the user's terminal. Adding `shell: true` to both the command availability check and the spawn calls ensures we use the user's full shell environment.
1 parent 65826bc commit 89e52e0

File tree

1 file changed

+23
-8
lines changed

1 file changed

+23
-8
lines changed

src/node/services/editorService.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ import { isSSHRuntime } from "@/common/types/runtime";
44
import { log } from "@/node/services/log";
55
import { createRuntime } from "@/node/runtime/runtimeFactory";
66

7+
/**
8+
* Quote a string for safe use in shell commands.
9+
* Uses single quotes with proper escaping for embedded single quotes.
10+
*/
11+
function shellQuote(value: string): string {
12+
if (value.length === 0) return "''";
13+
return "'" + value.replace(/'/g, "'\"'\"'") + "'";
14+
}
15+
716
export interface EditorConfig {
817
editor: string;
918
customCommand?: string;
@@ -81,20 +90,24 @@ export class EditorService {
8190
const resolvedPath = await runtime.resolvePath(targetPath);
8291

8392
// Build the remote command: code --remote ssh-remote+host /remote/path
84-
const args = ["--remote", `ssh-remote+${runtimeConfig.host}`, resolvedPath];
93+
// Quote the path to handle spaces; the remote host arg doesn't need quoting
94+
const shellCmd = `${editorCommand} --remote ${shellQuote(`ssh-remote+${runtimeConfig.host}`)} ${shellQuote(resolvedPath)}`;
8595

86-
log.info(`Opening SSH path in editor: ${editorCommand} ${args.join(" ")}`);
87-
const child = spawn(editorCommand, args, {
96+
log.info(`Opening SSH path in editor: ${shellCmd}`);
97+
const child = spawn(shellCmd, [], {
8898
detached: true,
8999
stdio: "ignore",
100+
shell: true,
90101
});
91102
child.unref();
92103
} else {
93-
// Local - just open the path
94-
log.info(`Opening local path in editor: ${editorCommand} ${targetPath}`);
95-
const child = spawn(editorCommand, [targetPath], {
104+
// Local - just open the path (quote to handle spaces)
105+
const shellCmd = `${editorCommand} ${shellQuote(targetPath)}`;
106+
log.info(`Opening local path in editor: ${shellCmd}`);
107+
const child = spawn(shellCmd, [], {
96108
detached: true,
97109
stdio: "ignore",
110+
shell: true,
98111
});
99112
child.unref();
100113
}
@@ -108,11 +121,13 @@ export class EditorService {
108121
}
109122

110123
/**
111-
* Check if a command is available in the system PATH
124+
* Check if a command is available in the system PATH.
125+
* Uses shell: true to ensure we get the full PATH from user's shell profile,
126+
* which is necessary for commands installed via Homebrew or similar.
112127
*/
113128
private isCommandAvailable(command: string): boolean {
114129
try {
115-
const result = spawnSync("which", [command], { encoding: "utf8" });
130+
const result = spawnSync("which", [command], { encoding: "utf8", shell: true });
116131
return result.status === 0;
117132
} catch {
118133
return false;

0 commit comments

Comments
 (0)