Root cause: On macOS, GUI applications don't inherit shell profile modifications (.zshrc, .bashrc). When OpenWork spawns the opencode engine, the PATH doesn't include paths like
  /opt/homebrew/bin where Homebrew-installed tools like npx reside.

  The fix (packages/desktop/src-tauri/src/paths.rs): Added a common_tool_paths() function that returns typical locations where user-installed tools are found:
  - macOS: /opt/homebrew/bin, /usr/local/bin, ~/.nvm/current/bin, ~/.volta/bin, ~/.bun/bin, ~/.cargo/bin, etc.
  - Linux: Similar paths adapted for Linux conventions
  - Windows: volta, pnpm, cargo, npm global paths

  These paths are now prepended to the PATH environment variable when spawning processes.

  To test the fix: Build and run the app, then try enabling your Playwright MCP server - it should now find npx correctly without needing to specify the full path.
This commit is contained in:
Aayush Prajapati
2026-02-15 01:53:50 +05:30
committed by GitHub
parent 799bc194bd
commit 4cabebd2a9
2 changed files with 107 additions and 1 deletions

12
package-lock.json generated Normal file
View File

@@ -0,0 +1,12 @@
{
"name": "@different-ai/openwork-workspace",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@different-ai/openwork-workspace",
"version": "0.0.0"
}
}
}

View File

@@ -125,6 +125,89 @@ pub fn sidecar_path_candidates(
unique
}
/// Returns common paths where user-installed tools are typically located.
/// On macOS, GUI apps don't inherit shell profile modifications (.zshrc, .bashrc),
/// so tools installed via Homebrew, nvm, volta, etc. won't be found unless we
/// explicitly include these common locations.
fn common_tool_paths() -> Vec<PathBuf> {
let mut paths = Vec::new();
#[cfg(target_os = "macos")]
{
// Homebrew paths (Apple Silicon and Intel)
paths.push(PathBuf::from("/opt/homebrew/bin"));
paths.push(PathBuf::from("/opt/homebrew/sbin"));
paths.push(PathBuf::from("/usr/local/bin"));
paths.push(PathBuf::from("/usr/local/sbin"));
// User-specific paths for common version managers
if let Some(home) = home_dir() {
// nvm, fnm (Node version managers)
paths.push(home.join(".nvm/current/bin"));
paths.push(home.join(".fnm/current/bin"));
// volta
paths.push(home.join(".volta/bin"));
// pnpm
paths.push(home.join("Library/pnpm"));
// bun
paths.push(home.join(".bun/bin"));
// cargo/rustup
paths.push(home.join(".cargo/bin"));
// pyenv
paths.push(home.join(".pyenv/shims"));
// local bin
paths.push(home.join(".local/bin"));
}
}
#[cfg(target_os = "linux")]
{
paths.push(PathBuf::from("/usr/local/bin"));
paths.push(PathBuf::from("/usr/local/sbin"));
if let Some(home) = home_dir() {
// nvm, fnm
paths.push(home.join(".nvm/current/bin"));
paths.push(home.join(".fnm/current/bin"));
// volta
paths.push(home.join(".volta/bin"));
// pnpm
paths.push(home.join(".local/share/pnpm"));
// bun
paths.push(home.join(".bun/bin"));
// cargo
paths.push(home.join(".cargo/bin"));
// pyenv
paths.push(home.join(".pyenv/shims"));
// local bin
paths.push(home.join(".local/bin"));
}
}
#[cfg(windows)]
{
if let Some(home) = home_dir() {
// volta
paths.push(home.join(".volta/bin"));
// pnpm
if let Some(local) = env::var_os("LOCALAPPDATA") {
paths.push(PathBuf::from(local).join("pnpm"));
}
// bun
paths.push(home.join(".bun/bin"));
// cargo
paths.push(home.join(".cargo/bin"));
}
// npm global
if let Some(app_data) = env::var_os("APPDATA") {
paths.push(PathBuf::from(app_data).join("npm"));
}
}
paths
}
pub fn prepended_path_env(prefixes: &[PathBuf]) -> Option<std::ffi::OsString> {
let mut entries = Vec::<PathBuf>::new();
@@ -134,8 +217,19 @@ pub fn prepended_path_env(prefixes: &[PathBuf]) -> Option<std::ffi::OsString> {
}
}
// Add common tool paths that may not be in the GUI app's inherited PATH
for path in common_tool_paths() {
if path.is_dir() && !entries.contains(&path) {
entries.push(path);
}
}
if let Some(existing) = env::var_os("PATH") {
entries.extend(env::split_paths(&existing));
for path in env::split_paths(&existing) {
if !entries.contains(&path) {
entries.push(path);
}
}
}
if entries.is_empty() {