Launchd shell env bootstrap
Skill kjuhwa/skills-hub/skills/electron/launchd-shell-env-bootstrap
Electron apps launched from Finder/Dock on macOS inherit a minimal launchd PATH — spawn the user's login+interactive shell, parse its env, merge it into process.env so brew/nvm/pyenv work for the agent.From its SKILL.md
npx -y skills add kjuhwa/skills-hub --skill launchd-shell-env-bootstrapAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
3.7 KB, 846 tokens by cl100k_base, as published. Nobody here has run it
Load user's shell env into an Electron macOS app
When to use
- Electron app that spawns dev-tool subprocesses (git, gh, brew, pyenv, nvm-managed node, etc.).
- macOS users launch your app from Finder/Dock (not Terminal) and the agent can't find their tools.
- Don't want to hand-roll "add /usr/local/bin to PATH" - you want whatever
zsh -l -iwould give them.
How it works
- Only run on macOS (
process.platform === 'darwin'); skip on Linux (desktop environments usually give a full env), skip on Windows (different model entirely). - Skip if
VITE_DEV_SERVER_URLis set (dev mode runs from Terminal, env is already present). execSync('${SHELL} -l -i -c "echo __ENV_START__ && env"'):-l= login shell (sources.zprofile,.bash_profile).-i= interactive shell (sources.zshrc,.bashrc).- The
__ENV_START__marker separates shell startup noise from the env dump.
- Pass minimal env to the child:
HOME,USER,SHELL,TERM=xterm-256color,TMPDIR. SetAPPLE_SUPPRESS_DEVELOPER_TOOL_POPUP=1+GIT_TERMINAL_PROMPT=0to avoid interactive popups during the env dump. - Split after
__ENV_START__, parseKEY=VALUElines, merge intoprocess.env. - Filter out unsafe imports like
VITE_*- those would make the packaged app try to load the renderer from a dev server. - Use
timeout: 5000- if user has a broken shell, don't hang app startup forever.
Example
import { execSync } from 'child_process';
export function loadShellEnv(): void {
if (process.platform !== 'darwin') return;
if (process.env.VITE_DEV_SERVER_URL) return;
const shell = process.env.SHELL || '/bin/zsh';
try {
const out = execSync(`${shell} -l -i -c 'echo __ENV_START__ && env'`, {
encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'],
env: {
HOME: process.env.HOME, USER: process.env.USER, SHELL: shell,
TERM: 'xterm-256color', TMPDIR: process.env.TMPDIR,
APPLE_SUPPRESS_DEVELOPER_TOOL_POPUP: '1', GIT_TERMINAL_PROMPT: '0',
},
});
const env = out.split('__ENV_START__')[1] || '';
for (const line of env.trim().split('\n')) {
const eq = line.indexOf('='); if (eq < 1) continue;
const key = line.slice(0, eq); if (key.startsWith('VITE_')) continue;
process.env[key] = line.slice(eq + 1);
}
} catch { /* log, continue */ }
}
Gotchas
- Must run BEFORE any
importthat reads env (require ordering matters). In the main file, make this the literal first import side-effect. - Interactive shells can emit color codes / prompts; the marker pattern prevents false positives but consider also unsetting
PS1/PROMPT. zsh -l -iis slow (50-200ms) on some setups. That's the cost of being right. Cache the result across app launches keyed onzshrcmtime if startup budget matters.- Be wary of loading user-defined vars that can break your app (
NODE_OPTIONS=--inspect-brk) - filter aggressively if you've seen breakage. - On packaged builds, blocking startup on this is fine; in dev, terminal already has env, so skip.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.