Read paths from an interactive & login shell#5774
Conversation
6136df0 to
07981df
Compare
There was a problem hiding this comment.
Pull Request Overview
This PR adds functionality to read PATH environment variables from an interactive and login shell, addressing the common issue where GUI-launched applications don't inherit the user's shell PATH configuration.
Key changes:
- New
paths.rsmodule that asynchronously retrieves PATH from the user's shell using login and interactive modes - Integration in
rmcp_developer.rsto override PATH before executing shell commands - Lazy initialization with caching to avoid repeatedly spawning shells
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| crates/goose-mcp/src/developer/paths.rs | New module implementing shell PATH retrieval with platform-specific logic for Unix and Windows, using OnceCell for caching |
| crates/goose-mcp/src/developer/rmcp_developer.rs | Integrates shell PATH retrieval by calling get_shell_path_dirs() and setting PATH environment variable before command execution |
| crates/goose-mcp/src/developer/mod.rs | Exposes the new paths module as public |
| let output = output.map_err(|e| anyhow::anyhow!("Failed to execute shell command: {}", e))?; | ||
|
|
||
| if !output.status.success() { | ||
| let stderr = String::from_utf8_lossy(&output.stderr); |
There was a problem hiding this comment.
The -NoProfile flag prevents loading PowerShell profiles, which means this won't get the user's customized PATH. This is inconsistent with the Unix implementation which uses -l (login) to load profiles. Consider removing -NoProfile or using -NoLogo instead if you want to suppress the banner while still loading profiles.
| .map_err(|e| anyhow::anyhow!("Invalid UTF-8 in shell output: {}", e))? | ||
| .trim() | ||
| .to_string(); | ||
|
|
There was a problem hiding this comment.
The -l -i flag combination will fail with fish shell, which doesn't support using -l with -c. Consider using only -l (login mode sources profile files) or detecting the shell type and adjusting flags accordingly.
There was a problem hiding this comment.
I blindly accepted copilot's suggestion, but if I try it myself this works just fine:
% fish -i -l -c 'echo $PATH'
/usr/local/bin ...
| async fn get_shell_path_async() -> Result<String> { | ||
| let shell = env::var("SHELL").unwrap_or_else(|_| { | ||
| if cfg!(windows) { | ||
| "cmd".to_string() |
There was a problem hiding this comment.
could use a macro, but then I think this is probably fine (and probably less lines of code)
| }); | ||
|
|
||
| { | ||
| #[cfg(windows)] |
There was a problem hiding this comment.
if using macro here, could use it for if statement above?
|
I think this is good - the copilot feedback if right is quite helpful (ie powershell woudln't pick things up, and fish shell would break with -c - not sure if true though!) |
| .map_err(|e| anyhow::anyhow!("Invalid UTF-8 in shell output: {}", e))? | ||
| .trim() | ||
| .to_string(); | ||
|
|
|
@DOsinga fish shell has a lot of fans! |
crates/goose-cli/src/cli.rs
Outdated
| Some(Command::Mcp { server }) => match server { | ||
| McpCommand::AutoVisualiser => serve(AutoVisualiserRouter::new()).await?, | ||
| McpCommand::ComputerController => serve(ComputerControllerServer::new()).await?, | ||
| McpCommand::Memory => serve(MemoryServer::new()).await?, | ||
| McpCommand::Tutorial => serve(TutorialServer::new()).await?, | ||
| McpCommand::Developer => serve(DeveloperServer::new()).await?, |
There was a problem hiding this comment.
Logging setup was removed for MCP commands. This means MCP servers started via goose-cli won't have logging configured. Consider adding crate::logging::setup_logging(Some(&format!("mcp-{}", server.name())), None)?; before the match statement.
| Some(Command::Mcp { server }) => match server { | |
| McpCommand::AutoVisualiser => serve(AutoVisualiserRouter::new()).await?, | |
| McpCommand::ComputerController => serve(ComputerControllerServer::new()).await?, | |
| McpCommand::Memory => serve(MemoryServer::new()).await?, | |
| McpCommand::Tutorial => serve(TutorialServer::new()).await?, | |
| McpCommand::Developer => serve(DeveloperServer::new()).await?, | |
| Some(Command::Mcp { server }) => { | |
| crate::logging::setup_logging(Some(&format!("mcp-{}", server.name())), None)?; | |
| match server { | |
| McpCommand::AutoVisualiser => serve(AutoVisualiserRouter::new()).await?, | |
| McpCommand::ComputerController => serve(ComputerControllerServer::new()).await?, | |
| McpCommand::Memory => serve(MemoryServer::new()).await?, | |
| McpCommand::Tutorial => serve(TutorialServer::new()).await?, | |
| McpCommand::Developer => serve(DeveloperServer::new()).await?, | |
| } |
| let output = Command::new(shell) | ||
| .args(["-l", "-i", "-c", "echo $PATH"]) |
There was a problem hiding this comment.
The -i flag is not supported by fish shell, which will cause this command to fail for fish users. Consider conditionally using ["-l", "-c", "echo $PATH"] for fish and ["-l", "-i", "-c", "echo $PATH"] for bash/zsh.
| let output = Command::new(shell) | |
| .args(["-l", "-i", "-c", "echo $PATH"]) | |
| // Use different args for fish shell, which does not support -i | |
| let shell_name = std::path::Path::new(shell) | |
| .file_stem() | |
| .and_then(|s| s.to_str()) | |
| .unwrap_or(""); | |
| let args: [&str; 4] = if shell_name == "fish" { | |
| ["-l", "", "-c", "echo $PATH"] | |
| } else { | |
| ["-l", "-i", "-c", "echo $PATH"] | |
| }; | |
| // Remove empty string from args for fish | |
| let args: Vec<&str> = args.iter().filter(|&&a| !a.is_empty()).copied().collect(); | |
| let output = Command::new(shell) | |
| .args(&args) |
| McpCommand::ComputerController => serve(ComputerControllerServer::new()).await?, | ||
| McpCommand::Memory => serve(MemoryServer::new()).await?, | ||
| McpCommand::Tutorial => serve(TutorialServer::new()).await?, | ||
| McpCommand::Developer => serve(DeveloperServer::new()).await?, |
There was a problem hiding this comment.
The goose-cli Developer server doesn't enable shell PATH extension like goose-server does. This creates inconsistent behavior - users will get different PATH environments depending on which binary they use. Either add .extend_path_with_shell(true).bash_env_file(Some(Paths::config_dir().join(\".bash_env\"))) here to match goose-server, or document why the CLI version intentionally has different behavior.
There was a problem hiding this comment.
yes that is the idea!
| fn safe_truncate(s: &str, max_chars: usize) -> String { | ||
| if s.chars().count() <= max_chars { | ||
| s.to_string() | ||
| } else { | ||
| let truncated: String = s.chars().take(max_chars.saturating_sub(3)).collect(); | ||
| format!("{}...", truncated) | ||
| } | ||
| } |
There was a problem hiding this comment.
Duplicating safe_truncate creates a maintenance burden - changes to the original won't propagate here. Consider creating a shared utility crate that both goose and goose-mcp can depend on, or re-evaluate whether breaking the goose dependency is worth this duplication.
| fn safe_truncate(s: &str, max_chars: usize) -> String { | |
| if s.chars().count() <= max_chars { | |
| s.to_string() | |
| } else { | |
| let truncated: String = s.chars().take(max_chars.saturating_sub(3)).collect(); | |
| format!("{}...", truncated) | |
| } | |
| } | |
| use goose_util::safe_truncate; |
…xt-test * 'main' of github.com:block/goose: chore: Add Adrian Cole to Maintainers (#5815) [MCP-UI] Proxy and Better Message Handling (#5487) Release 1.15.0 Document New Window menu in macOS dock (#5811) Catch cron errors (#5707) feat/fix Re-enabled WAL with commit transaction management (Linux Verification Requested) (#5793) chore: remove autopilot experimental feature (#5781) Read paths from an interactive & login shell (#5774) docs: acp clients (#5800)
* main: feat/fix Re-enabled WAL with commit transaction management (Linux Verification Requested) (#5793) chore: remove autopilot experimental feature (#5781) Read paths from an interactive & login shell (#5774) docs: acp clients (#5800) Provider error proxy for simulating various types of errors (#5091) chore: Add links to maintainer profiles (#5788) Quick fix for community all stars script (#5798) Document Mistral AI provider (#5799) docs: Add Community Stars recipe script and txt file (#5776)
* main: (33 commits) fix: support Gemini 3's thought signatures (#5806) chore: Add Adrian Cole to Maintainers (#5815) [MCP-UI] Proxy and Better Message Handling (#5487) Release 1.15.0 Document New Window menu in macOS dock (#5811) Catch cron errors (#5707) feat/fix Re-enabled WAL with commit transaction management (Linux Verification Requested) (#5793) chore: remove autopilot experimental feature (#5781) Read paths from an interactive & login shell (#5774) docs: acp clients (#5800) Provider error proxy for simulating various types of errors (#5091) chore: Add links to maintainer profiles (#5788) Quick fix for community all stars script (#5798) Document Mistral AI provider (#5799) docs: Add Community Stars recipe script and txt file (#5776) chore: incorporate LF feedback (#5787) docs: quick launcher (#5779) Bump auto scroll threshold (#5738) fix: add one-time cleanup for linux hermit locking issues (#5742) Don't show update tray icon if GOOSE_VERSION is set (#5750) ...
Signed-off-by: Blair Allan <Blairallan@icloud.com>
|
It feels weird to have goose use |
I don't think there is an ideal answer here. users expect certain things to carry over from their shell settings |
No description provided.