fix(desktop): resolve login shell environment at Electron startup

On macOS, apps launched from Finder/Dock inherit a minimal environment
(PATH is just /usr/bin:/bin:/usr/sbin:/sbin). This caused two problems:
1. Agent binaries like codex were not detected (findExecutable failed)
2. Terminals spawned by Paseo had no access to user-installed tools
   (node, bun, direnv — all "command not found")

Fix: at Electron startup, spawn the user's login shell and capture its
full environment via JSON.stringify(process.env) using UUID markers.
This is the same battle-tested approach VS Code uses. The resolved
environment is merged into process.env before the daemon starts, so
all child processes — agents, terminals, git operations — inherit the
correct environment automatically.

This replaces the previous approach of invoking resolveShellEnv() (via
the shell-env npm package) at multiple scattered call sites. Now there
is a single source of truth: process.env is enriched once at startup.

Changes:
- New: packages/desktop/src/login-shell-env.ts (VS Code approach)
- Removed: resolveShellEnv(), shell-env dependency, $SHELL -lic probes
- Simplified: applyProviderEnv() and findExecutable() now trust process.env
This commit is contained in:
Mohamed Boudra
2026-04-05 15:11:18 +07:00
parent 84638ecd9c
commit 6907f6e71d
9 changed files with 140 additions and 205 deletions
@@ -66,7 +66,7 @@ describe("applyProviderEnv", () => {
},
};
const env = applyProviderEnv(base, runtime, {});
const env = applyProviderEnv(base, runtime);
expect(env.PATH).toBe("/usr/bin");
expect(env.HOME).toBe("/custom/home");
@@ -74,21 +74,11 @@ describe("applyProviderEnv", () => {
expect(Object.keys(env).length).toBeGreaterThanOrEqual(3);
});
test("shell env PATH wins over base env PATH", () => {
const base = { PATH: "/usr/bin:/bin" };
const shellEnv = { PATH: "/usr/local/bin:/usr/bin:/bin:/home/user/.nvm/bin" };
const env = applyProviderEnv(base, undefined, shellEnv);
expect(env.PATH).toBe("/usr/local/bin:/usr/bin:/bin:/home/user/.nvm/bin");
});
test("runtimeSettings env wins over shell env", () => {
test("runtimeSettings env wins over base env", () => {
const base = { PATH: "/usr/bin" };
const shellEnv = { PATH: "/usr/local/bin:/usr/bin" };
const runtime: ProviderRuntimeSettings = { env: { PATH: "/custom/path" } };
const env = applyProviderEnv(base, runtime, shellEnv);
const env = applyProviderEnv(base, runtime);
expect(env.PATH).toBe("/custom/path");
});
@@ -103,7 +93,7 @@ describe("applyProviderEnv", () => {
CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING: "true",
};
const env = applyProviderEnv(base, undefined, {});
const env = applyProviderEnv(base);
expect(env.PATH).toBe("/usr/bin");
expect(env.CLAUDECODE).toBeUndefined();