fix: resolve user login PATH for MCP extension subprocesses#7896
fix: resolve user login PATH for MCP extension subprocesses#7896DOsinga merged 3 commits intoblock:mainfrom
Conversation
When Goose Desktop launches from Dock/Spotlight, goosed inherits a minimal PATH (/usr/bin:/bin:/usr/sbin:/sbin). The developer extension fixed this in block#5774 by resolving the user's login shell PATH, but that fix lives in the goose crate. The MCP extensions in goose-mcp never got the same treatment. This ports resolve_login_shell_path() and user_login_path() to goose-mcp/src/subprocess.rs and injects the resolved PATH at all subprocess spawn points in computercontroller: - Shell script execution (automation_script) - Peekaboo commands (macOS UI automation) The PATH is resolved once via a login shell and cached for the process lifetime, matching the developer extension's approach. Fixes block#5915
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 56a55000e7
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
crates/goose-mcp/src/subprocess.rs
Outdated
| let shell = if PathBuf::from("/bin/bash").is_file() { | ||
| "/bin/bash".to_string() | ||
| } else { | ||
| std::env::var("SHELL").unwrap_or_else(|_| "sh".to_string()) |
There was a problem hiding this comment.
Use the user's configured shell for PATH resolution
resolve_login_shell_path() always prefers /bin/bash when present, which ignores $SHELL for users on zsh/fish and can drop shell-specific PATH entries; because the new callers then overwrite PATH with this value, MCP subprocesses can lose access to tools that are available in the user's actual login shell. This is reproducible on non-bash setups where PATH is configured only in shell-specific startup files.
Useful? React with 👍 / 👎.
| if let Some(path) = user_login_path() { | ||
| cmd.env("PATH", path); |
There was a problem hiding this comment.
Preserve runtime PATH adjustments before invoking peekaboo
This override can undo ensure_peekaboo()'s install-time PATH repair path: auto_install_peekaboo() may call std::env::set_var("PATH", ...) when brew binaries are not initially discoverable, but run_peekaboo_cmd() now replaces PATH with the cached login-shell value, so ensure_peekaboo() can succeed and the subsequent peekaboo launch still fail in that environment.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4a8f6493a9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| .env("AGENT", "goose"); | ||
| #[cfg(not(windows))] | ||
| if let Some(path) = user_login_path() { | ||
| cmd.env("PATH", path); |
There was a problem hiding this comment.
Preserve inherited PATH for automation script subprocesses
Overwriting PATH here with the cached login-shell value drops any runtime PATH entries already present in the goosed process, so tools available only via session-specific environments (for example direnv/nix/venv paths when Goose is launched from a terminal) become unresolvable in automation_script. This is a regression from the previous inherit-by-default behavior; merging with the existing PATH instead of replacing it avoids breaking those workflows.
Useful? React with 👍 / 👎.
Address three review comments: 1. resolve_login_shell_path() now prefers $SHELL over hardcoded /bin/bash, so zsh/fish users source the correct profile files. 2. New merged_path() function prepends login shell PATH entries while preserving runtime additions (direnv, nix, ensure_peekaboo installs). 3. Both automation_script and run_peekaboo_cmd callers switched from user_login_path() (replace) to merged_path() (merge + deduplicate).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d0d4b06ecf
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| std::process::Command::new(&shell) | ||
| .args(["-l", "-i", "-c", "echo $PATH"]) |
There was a problem hiding this comment.
Handle fish-style PATH output when probing login shells
resolve_login_shell_path() assumes every shell prints $PATH as one colon-delimited string, but fish exposes PATH as a list, so echo $PATH emits space-separated entries. merged_path() then treats that whole line as a single directory and user bins like /opt/homebrew/bin or ~/.cargo/bin never make it into the exported PATH. On macOS/Linux systems whose login shell is fish, the subprocesses this patch is meant to fix (peekaboo, automation scripts) will still fail to find user-installed tools.
Useful? React with 👍 / 👎.
| // Deduplicate: login shell entries first, then any current entries not already present. | ||
| let login_entries: Vec<&str> = login.split(':').collect(); | ||
| let mut seen: std::collections::HashSet<&str> = login_entries.iter().copied().collect(); | ||
| let mut merged = login_entries; | ||
| for entry in current.split(':') { |
There was a problem hiding this comment.
Keep the current PATH ahead of login-shell entries
merged_path() now prepends the login-shell directories ahead of the process's existing PATH, which changes command resolution whenever Goose is started inside an activated venv, direnv, or nix shell. In those sessions, computer_control subprocesses will resolve python, node, etc. from the login shell instead of the active environment even though the correct shims were already first in the inherited PATH, so this is still a behavior regression from the previous inherit-by-default behavior.
Useful? React with 👍 / 👎.
DOsinga
left a comment
There was a problem hiding this comment.
Clean port of the existing developer-extension fix into goose-mcp. The merged_path() dedup logic is a nice improvement over the original. Good to go.
|
All CI green, approval in place, no outstanding comments — ready to merge whenever you get a chance. Thanks @DOsinga 🙏 |
Signed-off-by: Cameron Yick <[email protected]>
Summary
When Goose Desktop launches from Dock or Spotlight on macOS,
goosedinherits a minimal PATH:This means MCP extensions cannot find user-installed tools (
gh,brew,claude,cargo, etc.) unless the user provides absolute paths.The developer extension fixed this in #5774 by resolving the user's login shell PATH once and injecting it into subprocesses. But that fix lives in the
goosecrate — the MCP extensions ingoose-mcpnever got the same treatment.Changes
crates/goose-mcp/src/subprocess.rsresolve_login_shell_path()— spawns a login shell once to recover the full PATHuser_login_path()— caches the result in aOnceLockfor the process lifetimecrates/goose/src/agents/platform_extensions/developer/shell.rscrates/goose-mcp/src/computercontroller/mod.rsautomation_script)Why duplicate instead of shared crate?
goose-mcpdoes not depend ongoose— they are sibling crates. Both already duplicateSubprocessExtin their respectivesubprocess.rsfiles. This follows the existing pattern. A shared utility crate could consolidate both in a follow-up.Testing
cargo check -p goose-mcp— cleancargo test -p goose-mcp— 46/46 passedgh,brew,git,jq,cargoall resolve correctly from computercontroller shell after the changeFixes #5915