Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 32 additions & 15 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,54 @@ import { resolveUserShell } from './user-shell.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// When launched from a .desktop file, PATH is minimal (/usr/bin:/bin).
// Resolve the user's full login-interactive shell PATH so spawned PTYs
// can find CLI tools like claude, codex, gemini, etc.
// When launched from a .desktop file (e.g. AppImage), the environment is
// minimal — often just PATH=/usr/bin:/bin. Resolve the user's full
// login-interactive shell environment and merge it into process.env so
// spawned PTYs can find CLI tools (claude, codex, gemini, etc.) and
// inherit other expected variables (SSH_AGENT_LAUNCHER, KUBECONFIG, etc.).
//
// Uses -ilc (interactive + login) to source both .zprofile/.profile AND
// .zshrc/.bashrc, where version managers (nvm, volta, fnm) add to PATH.
// Sentinel markers isolate PATH from noisy shell init output.
// A perl one-liner dumps every env var as null-delimited key=value pairs,
// bounded by sentinel markers to isolate the data from noisy shell init.
//
// Trade-off: -i (interactive) triggers .zshrc side effects (compinit, conda,
// welcome messages). Login-only (-lc) would be quieter but would miss tools
// that are only added to PATH in .bashrc/.zshrc (e.g. nvm). We accept the
// side effects since the sentinel-based parsing discards all other output.
function fixPath(): void {
// Another trade-off: inheriting the *full* environment (rather than just PATH)
// can pull in large variables (certificates, tokens, kubeconfig). We set a
// generous maxBuffer and fall back to the original environment on failure.
function fixEnv(): void {
Comment on lines 28 to +34
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment above this function still describes resolving only PATH and using sentinel-based parsing to isolate PATH output, but the implementation now captures and applies the full environment via env -0. Please update the comment to match the new behavior (and any updated trade-offs).

Copilot uses AI. Check for mistakes.
if (process.platform === 'win32') return;
try {
const loginShell = resolveUserShell();
const sentinel = '__PCODE_PATH__';
const result = execFileSync(loginShell, ['-ilc', `printf "${sentinel}%s${sentinel}" "$PATH"`], {
encoding: 'utf8',
timeout: 5000,
});
const match = result.match(new RegExp(`${sentinel}(.+?)${sentinel}`));
if (match?.[1]) {
process.env.PATH = match[1];
const sentinel = '__PCODE_ENV__';
const result = execFileSync(
loginShell,
[
'-ilc',
`printf '${sentinel}' && perl -e 'print "$_=$ENV{$_}\\0" for keys %ENV' && printf '${sentinel}'`,
],
{ encoding: 'utf8', timeout: 5000, maxBuffer: 10 * 1024 * 1024 },
);
Comment on lines +39 to +46
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

execFileSync defaults to a ~1MB maxBuffer. Dumping the full login-shell environment (especially with large vars like certificates, tokens, kubeconfig, etc.) can exceed this and cause the call to throw, leaving process.env unchanged. Consider setting an explicit, larger maxBuffer (similar to MAX_BUFFER used in electron/ipc/git.ts) or handling this failure mode more explicitly.

Copilot uses AI. Check for mistakes.
const startIdx = result.indexOf(sentinel);
const endIdx = result.lastIndexOf(sentinel);
if (startIdx === -1 || endIdx === -1 || startIdx === endIdx) return;

const envBlock = result.slice(startIdx + sentinel.length, endIdx);
for (const entry of envBlock.split('\0')) {
if (!entry) continue;
const eqIdx = entry.indexOf('=');
if (eqIdx <= 0) continue;
process.env[entry.slice(0, eqIdx)] = entry.slice(eqIdx + 1);
}
} catch (err) {
console.warn('[fixPath] Failed to resolve login shell PATH:', err);
console.warn('[fixEnv] Failed to resolve login shell environment:', err);
}
}

fixPath();
fixEnv();

// Verify that preload.cjs ALLOWED_CHANNELS stays in sync with the IPC enum.
// Logs a warning in dev if they drift — catches mismatches before they hit users.
Expand Down
Loading