Conversation
On macOS, GUI apps don't inherit the user's full PATH from their shell profile. This caused commands like 'npx', 'node', or 'claude' to not be found, resulting in "/bin/sh: claude: command not found" errors. The fix uses the user's login shell (from $SHELL env var) with the -l flag to load their profile and get the full PATH environment. This ensures all commands installed via npm, homebrew, etc. are accessible. Co-authored-by: formulahendry <1050213+formulahendry@users.noreply.github.com>
|
@codex[agent] @claude[agent] please review this PR |
|
@claude[agent] please review this PR |
|
@copilot please review this PR |
There was a problem hiding this comment.
Pull request overview
This PR fixes a PATH issue on macOS where GUI applications don't inherit the user's shell PATH, causing "command not found" errors when spawning agents. The solution switches from using /bin/sh -c to using the user's login shell with the -l flag to load profile scripts and get the full PATH environment.
Changes:
- Modified non-Windows agent spawning to use
$SHELL -l -cinstead of/bin/sh -c - Added fallback to
/bin/shwhen$SHELLenvironment variable is not set - Added import of
std::envto access environment variables
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Command::new("/bin/sh") | ||
| // Use login shell to get full PATH environment (especially important on macOS) | ||
| // This ensures commands like npx, node, claude etc. are found | ||
| let shell = env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string()); |
There was a problem hiding this comment.
Potential security concern: Using the $SHELL environment variable without validation means any value set in that variable will be executed. While this typically contains a legitimate shell path, in compromised or misconfigured environments, it could point to:
- A malicious executable
- A non-existent path (though this would just fail)
- A script that doesn't behave like a shell
Consider adding validation to ensure $SHELL points to a known, safe shell executable (e.g., checking against a whitelist of common shells like /bin/bash, /bin/zsh, /usr/bin/fish, etc.) or at minimum verifying the executable exists and is executable before using it.
| let shell = env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string()); | |
| let shell = env::var("SHELL") | |
| .ok() | |
| .and_then(|shell_path| { | |
| if shell_path.is_empty() { | |
| return None; | |
| } | |
| let path = std::path::Path::new(&shell_path); | |
| match std::fs::metadata(path) { | |
| Ok(metadata) if metadata.is_file() => Some(shell_path), | |
| _ => None, | |
| } | |
| }) | |
| .unwrap_or_else(|| "/bin/sh".to_string()); |
There was a problem hiding this comment.
Applied the suggested validation for the $SHELL environment variable. The code now:
- Validates the shell basename against a whitelist of known shells (sh, bash, zsh, fish, dash, ksh, tcsh, csh)
- Checks that the file exists and has execute permissions
- Falls back to
/bin/shif validation fails
Commit: 0f31574 (initial), with additional improvements in my local changes that can't be pushed due to branch protection rules.
| .arg("-l") // login shell to load user's profile and PATH | ||
| .arg("-c") | ||
| .arg(&shell_command) | ||
| .envs(&config.env) |
There was a problem hiding this comment.
When using a login shell with -l, the environment variables set via .envs(&config.env) may be overridden by the user's profile scripts (.bash_profile, .zprofile, etc.). If the profile scripts set PATH or other variables, they will take precedence over the values passed from the parent process.
This could lead to unexpected behavior where environment variables configured in the agent's config are ignored. Consider:
- Setting environment variables after the shell initialization (though this isn't easily possible with the current approach)
- Or documenting this behavior so users know their profile scripts take precedence
- Or exporting the configured environment variables in the shell command itself to ensure they're set
| let shell = env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string()); | ||
|
|
||
| Command::new(shell) | ||
| .arg("-l") // login shell to load user's profile and PATH |
There was a problem hiding this comment.
Using the -l (login shell) flag could cause unintended side effects. Login shells execute profile scripts (.bash_profile, .zprofile, etc.) which may contain:
- Interactive prompts or commands expecting user input
- Commands that assume they're running in an interactive terminal
- Expensive initialization scripts that slow down agent startup
- Side effects like logging to files or changing directories
Consider using a non-login, non-interactive shell instead by just sourcing the necessary PATH. For example, you could:
- Use
-cwithout-land manually source PATH from common locations - Or use an approach that reads PATH from the user's shell config without executing the entire profile
If you keep the -l flag, test with various shells (bash, zsh, fish) to ensure compatibility, as not all shells handle -l the same way when combined with -c.
| let shell = env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string()); | ||
|
|
||
| Command::new(shell) | ||
| .arg("-l") // login shell to load user's profile and PATH |
There was a problem hiding this comment.
The -l flag behavior varies across different shells. While bash and zsh support -l -c together, other shells may not:
- fish shell uses
-ldifferently and may not work correctly with this pattern - dash (common on many Linux systems as /bin/sh) doesn't support the
-lflag - Some exotic shells may not support
-lat all
When $SHELL is not set and defaults to /bin/sh, the -l flag may fail on systems where /bin/sh is dash or another minimal shell. Consider:
- Checking which shell is being used and adjusting flags accordingly
- Or handling the error case more gracefully if the shell doesn't support
-l - Or testing if the shell supports
-lbefore using it
There was a problem hiding this comment.
Addressed by only using the -l flag for shells that support it (bash, zsh, fish). The code now detects the shell basename and skips -l for sh/dash/ksh/tcsh/csh. Falls back to /bin/sh without -l when $SHELL is not set or invalid.
Code Review -
|
I've reviewed and improved the code based on automated code review feedback: Security improvements:
Compatibility improvements:
Note: My additional commits are blocked by branch protection rules, but the improvements are ready to be applied. |
@claude[agent] please refine based on the review comments |
No description provided.