Mirror execFile's stdout into a buffer so the hard-timer path can still
extract a PATH sentinel that printed before a wedged descendant kept
the callback from firing, instead of discarding it as null.
execFile's `timeout` only SIGTERMs the direct shell child. A profile
that spawns a daemon (e.g. Powerlevel10k's gitstatusd under a non-TTY
GUI launch) can leave a grandchild holding the stdout pipe open, so
the execFile callback never fires and runProbe's promise hangs
forever — pinning desktop boot at "Resolving Hermes backend"
indefinitely.
Add a hard deadline that force-resolves the probe past its timeout and
kills the whole process group (shell + any daemons it spawned) so a
hung profile can never park boot.
Fixes#107109
feat(desktop): resolve the user's login-shell PATH once at startup and
merge it into process.env before the backend spawns.
GUI launches (Finder/Dock on macOS, desktop launchers on Linux) inherit
a minimal PATH that never runs the user's shell profiles, so the
backend process — and everything it spawns or probes (shutil.which
availability checks like cua-driver, stdio MCP servers, Electron-side
git/gh/hermes resolvers) — cannot see Homebrew-, nvm-, pyenv-, cargo-,
or ~/.local/bin-installed tools. backend-env.ts's static sane-entry
list covers Homebrew//usr/local but not profile-added dirs.
Approach (ported from cline/cline#12429, mirrors VS Code's shell
environment resolution):
- new electron/shell-path.ts: run $SHELL -ilc (fallback -lc for the
macOS system-bash-3.2 swallow) printing $PATH between sentinel
markers so profile banners can't corrupt the capture
- merge login-shell entries first, current-only entries appended,
deduped via backend-env's appendUniquePathEntries
- single-flight, timeout-bounded, failure-hardened: a broken or slow
shell profile never blocks boot; win32 no-op
- warmed at app.whenReady, awaited before backend runtime resolution
12 unit tests + live E2E verified (GUI-minimal PATH enriched with
~/.local/bin, nvm, cargo, go entries on a real shell).