From a001e91fb57415d69d3b3263334c5fda354d573e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20R=C3=B6diger?= Date: Fri, 3 Apr 2026 02:03:14 +0200 Subject: [PATCH 1/2] fix(main): resolve shell PATH not inherited in macOS GUI-launched app On macOS, Electron apps launched from Finder/Dock don't inherit the user's shell PATH, causing tools like npm/pnpm to be unresolvable. This reads the actual PATH from the user's login shell at startup using unique delimiters to safely ignore shell startup noise. Closes #506 --- src/main/index.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/index.ts b/src/main/index.ts index 688ffb36..565004df 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,9 +1,28 @@ +import { execFileSync } from "child_process"; import { app, BrowserWindow, dialog, ipcMain, Menu, MenuItemConstructorOptions, nativeTheme, shell } from "electron"; import * as fs from "fs"; import { createWriteStream } from "fs"; import * as http from "http"; import * as https from "https"; import * as path from "path"; + +// Fix PATH for macOS/Linux: GUI-launched Electron apps don't inherit the user's shell PATH. +// This reads the actual PATH from a login shell so tools like npm/pnpm can be found. +// Uses unique delimiters to extract PATH cleanly, ignoring any shell startup noise (banners, motd, etc.). +if (process.platform !== "win32") { + try { + const userShell = process.env.SHELL || "/bin/zsh"; + const output = execFileSync(userShell, ["-ilc", "echo __PPTB_PATH_START__$PATH__PPTB_PATH_END__"], { + encoding: "utf8", + }); + const match = output.match(/__PPTB_PATH_START__(.+)__PPTB_PATH_END__/); + if (match?.[1]) { + process.env.PATH = match[1]; + } + } catch { + // Silently ignore — keeps the default system PATH + } +} import { CONNECTION_CHANNELS, DATAVERSE_CHANNELS, From 62e40372512ecbcaed48a8a6e9d05df37ddb9b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20R=C3=B6diger?= Date: Fri, 3 Apr 2026 02:13:51 +0200 Subject: [PATCH 2/2] fix(main): address PR review feedback for PATH fix - Scope to macOS only (darwin) instead of all non-Windows platforms - Add timeout (3s) and maxBuffer to prevent hanging on slow shell startup - Use printenv PATH instead of $PATH for fish shell compatibility - Use indexOf/slice extraction instead of regex for multiline robustness - Restore default PATH on failure instead of silently ignoring --- src/main/index.ts | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 565004df..934b59c3 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -6,21 +6,31 @@ import * as http from "http"; import * as https from "https"; import * as path from "path"; -// Fix PATH for macOS/Linux: GUI-launched Electron apps don't inherit the user's shell PATH. +// Fix PATH for macOS: GUI-launched Electron apps (Finder/Dock) don't inherit the user's shell PATH. // This reads the actual PATH from a login shell so tools like npm/pnpm can be found. // Uses unique delimiters to extract PATH cleanly, ignoring any shell startup noise (banners, motd, etc.). -if (process.platform !== "win32") { +if (process.platform === "darwin") { + const defaultPath = process.env.PATH; try { const userShell = process.env.SHELL || "/bin/zsh"; - const output = execFileSync(userShell, ["-ilc", "echo __PPTB_PATH_START__$PATH__PPTB_PATH_END__"], { + const output = execFileSync(userShell, ["-ilc", 'printf "__PPTB_PATH_START__"; printenv PATH; printf "__PPTB_PATH_END__"'], { encoding: "utf8", - }); - const match = output.match(/__PPTB_PATH_START__(.+)__PPTB_PATH_END__/); - if (match?.[1]) { - process.env.PATH = match[1]; + timeout: 3000, + maxBuffer: 1024 * 1024, + }); + const startDelimiter = "__PPTB_PATH_START__"; + const endDelimiter = "__PPTB_PATH_END__"; + const startIndex = output.indexOf(startDelimiter); + const endIndex = output.indexOf(endDelimiter, startIndex + startDelimiter.length); + if (startIndex !== -1 && endIndex !== -1) { + const extractedPath = output.slice(startIndex + startDelimiter.length, endIndex).trim(); + if (extractedPath) { + process.env.PATH = extractedPath; + } } } catch { - // Silently ignore — keeps the default system PATH + // Restore default PATH on failure (timeout, shell error, etc.) + process.env.PATH = defaultPath; } } import {