Skip to content

fix: resolve user login PATH for MCP extension subprocesses#7896

Merged
DOsinga merged 3 commits intoblock:mainfrom
jeffa-block:jeffa/fix-mcp-path-resolution
Mar 26, 2026
Merged

fix: resolve user login PATH for MCP extension subprocesses#7896
DOsinga merged 3 commits intoblock:mainfrom
jeffa-block:jeffa/fix-mcp-path-resolution

Conversation

@jeffa-block
Copy link
Copy Markdown
Contributor

Summary

When Goose Desktop launches from Dock or Spotlight on macOS, goosed inherits a minimal PATH:

/Applications/Goose.app/Contents/Resources/bin:/usr/bin:/bin:/usr/sbin:/sbin

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 goose crate — the MCP extensions in goose-mcp never got the same treatment.

Changes

crates/goose-mcp/src/subprocess.rs

  • Adds resolve_login_shell_path() — spawns a login shell once to recover the full PATH
  • Adds user_login_path() — caches the result in a OnceLock for the process lifetime
  • Ported directly from crates/goose/src/agents/platform_extensions/developer/shell.rs

crates/goose-mcp/src/computercontroller/mod.rs

  • Injects the resolved PATH at both subprocess spawn points:
    • Shell script execution (automation_script)
    • Peekaboo commands (macOS UI automation)

Why duplicate instead of shared crate?

goose-mcp does not depend on goose — they are sibling crates. Both already duplicate SubprocessExt in their respective subprocess.rs files. This follows the existing pattern. A shared utility crate could consolidate both in a follow-up.

Testing

  • cargo check -p goose-mcp — clean
  • cargo test -p goose-mcp — 46/46 passed
  • Manually verified: gh, brew, git, jq, cargo all resolve correctly from computercontroller shell after the change

Fixes #5915

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
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +47 to +50
let shell = if PathBuf::from("/bin/bash").is_file() {
"/bin/bash".to_string()
} else {
std::env::var("SHELL").unwrap_or_else(|_| "sh".to_string())
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +1078 to +1079
if let Some(path) = user_login_path() {
cmd.env("PATH", path);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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).
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +60 to +61
std::process::Command::new(&shell)
.args(["-l", "-i", "-c", "echo $PATH"])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +104 to +108
// 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(':') {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator

@DOsinga DOsinga left a comment

Choose a reason for hiding this comment

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

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.

@jeffa-block
Copy link
Copy Markdown
Contributor Author

All CI green, approval in place, no outstanding comments — ready to merge whenever you get a chance. Thanks @DOsinga 🙏

@DOsinga DOsinga added this pull request to the merge queue Mar 26, 2026
Merged via the queue into block:main with commit aaaece1 Mar 26, 2026
21 checks passed
hydrosquall pushed a commit to hydrosquall/goose that referenced this pull request Mar 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Goose cannot resolve locally-installed claude CLI due to restricted PATH

2 participants