fix: start the macOS local terminal when the shell or spawn helper is broken (Support#1418)

This commit is contained in:
LukeGus committed 2026-10-07 16:56:52 -05:00
1 parent 679b61da9b
commit 18a594a92d
3 files changed
+132 -23

No files matched your search

+55 -9
View File
@@ -1,4 +1,16 @@
function resolveLocalShell(platform, requestedShell, env = process.env) {
const fs = require("node:fs");
const path = require("node:path");
function isUsableShell(file, exists) {
return !!file && (!path.isAbsolute(file) || exists(file));
}
function resolveLocalShell(
platform,
requestedShell,
env = process.env,
exists = fs.existsSync,
) {
if (platform === "win32") {
if (requestedShell === "wsl") {
return { file: "wsl.exe", args: [] };
@@ -9,13 +21,47 @@ function resolveLocalShell(platform, requestedShell, env = process.env) {
};
}
return {
file:
env.TERMIX_LOCAL_SHELL ||
env.SHELL ||
(platform === "darwin" ? "/bin/zsh" : "/bin/bash"),
args: ["-l"],
};
// A SHELL left pointing at an uninstalled shell makes the spawn fail.
const fallbacks =
platform === "darwin"
? ["/bin/zsh", "/bin/bash", "/bin/sh"]
: ["/bin/bash", "/bin/sh"];
const file =
[env.TERMIX_LOCAL_SHELL, env.SHELL, ...fallbacks].find((candidate) =>
isUsableShell(candidate, exists),
) || fallbacks[0];
return { file, args: ["-l"] };
}
module.exports = { resolveLocalShell };
/**
* node-pty starts every shell through its spawn-helper binary, and a packaged
* copy that lost its execute bit fails with "posix_spawnp failed". Returns
* how many helpers were fixed.
*/
function repairSpawnHelpers(ptyDir, fsImpl = fs) {
const dir = ptyDir.replace(/app\.asar(?!\.unpacked)/, "app.asar.unpacked");
const candidates = [
path.join(dir, "build", "Release", "spawn-helper"),
path.join(
dir,
"prebuilds",
`${process.platform}-${process.arch}`,
"spawn-helper",
),
];
let fixed = 0;
for (const helper of candidates) {
try {
const mode = fsImpl.statSync(helper).mode;
if ((mode & 0o111) === 0) {
fsImpl.chmodSync(helper, mode | 0o755);
fixed += 1;
}
} catch {
// Missing or read-only; nothing to fix here.
}
}
return fixed;
}
module.exports = { resolveLocalShell, repairSpawnHelpers };
+27 -12
View File
@@ -33,7 +33,7 @@ const { launchNativeRdp } = require("./native-rdp.cjs");
const { isCloseActiveTabInput } = require("./keyboard-shortcuts.cjs");
const { quitApp } = require("./app-quit.cjs");
const { selectLinuxPasswordStore } = require("./linux-password-store.cjs");
const { resolveLocalShell } = require("./local-shell.cjs");
const { resolveLocalShell, repairSpawnHelpers } = require("./local-shell.cjs");
const { registerLocalFileHandlers } = require("./local-files.cjs");
const localTerminalSessions = new Map();
@@ -3240,17 +3240,32 @@ ipcMain.handle("local-terminal-start", (event, dimensions = {}) => {
const rows = Math.min(300, Math.max(1, Number(dimensions.rows) || 24));
const sessionId = crypto.randomUUID();
const shellConfig = resolveLocalShell(process.platform, dimensions.shell);
const child = pty.spawn(shellConfig.file, shellConfig.args, {
name: "xterm-256color",
cols,
rows,
cwd: os.homedir(),
env: {
...process.env,
TERM: "xterm-256color",
COLORTERM: "truecolor",
},
});
const spawnShell = () =>
pty.spawn(shellConfig.file, shellConfig.args, {
name: "xterm-256color",
cols,
rows,
cwd: os.homedir(),
env: {
...process.env,
TERM: "xterm-256color",
COLORTERM: "truecolor",
},
});
let child;
try {
child = spawnShell();
} catch (error) {
const ptyDir = path.dirname(require.resolve("node-pty/package.json"));
if (
!String(error?.message).includes("posix_spawnp") ||
repairSpawnHelpers(ptyDir) === 0
) {
throw error;
}
logToFile("[local-terminal] restored the spawn-helper execute bit");
child = spawnShell();
}
const ownerId = event.sender.id;
const session = { ownerId, process: child, ready: false, buffered: "" };
localTerminalSessions.set(sessionId, session);