# Fix binary name resolution against custom PATH on macOS \(\#55672\) · gitcafe/zed

[View on GitCafe](https://git.cafe/gitcafe/zed/commit/d1f500edf15b5e6726710ab89199880e75ad45eb)

Repository: [gitcafe/zed](https://git.cafe/gitcafe/zed)

Visibility: public

Requested revision: d1f500edf15b5e6726710ab89199880e75ad45eb

Requested commit: d1f500edf15b5e6726710ab89199880e75ad45eb

Commit: d1f500edf15b5e6726710ab89199880e75ad45eb

Tree: d12fef0d73880bbd2c51247d2147565e132f8010

Author: G36maid

Committer: GitHub

## Message

```
Fix binary name resolution against custom PATH on macOS (#55672)

Closes #50536

## Summary

Addresses https://github.com/zed-industries/zed/issues/50536

- On macOS, `posix_spawnp` resolves programs against the parent
process's `cwd` and `environ` (via `getcwd` and `getenv("PATH")`),
ignoring the child's `current_dir` and `envp`. This causes two classes
of failures when Zed spawns external commands via the custom
`posix_spawnp`-based `Command`:
- **Bare names** (e.g. `"black"`): resolved via the parent's `PATH`, not
the child's — binaries only available in a project-specific PATH
(Nix/direnv) are not found.
- **Relative paths** (e.g. `"./script.sh"`, `"bin/test.sh"`): resolved
against the parent's `cwd`, not `current_dir` — only works when Zed's
cwd happens to match the project root.
- Added program resolution in `spawn_posix_spawn`
(`crates/util/src/command/darwin.rs`): before calling `posix_spawnp`,
resolve the program to an absolute path — bare names via
`which::which_in` against the child's PATH, relative paths via
`Path::join(current_dir)`. Falls back to the original program if
resolution fails.

## Root Cause

Apple's Libc implementation of `posix_spawnp` resolves the program path
using the parent process's context — `getcwd()` for relative paths and
`getenv("PATH")` for bare names — rather than the `current_dir` (set via
`posix_spawn_file_actions_addchdir_np`) or `envp` argument. The child's
working directory and environment only take effect **after** the binary
has already been located. This is a well-documented macOS behavior that
Rust's own `std::process::Command` works around by bypassing
`posix_spawn` when PATH is modified (see
[rust-lang/rust#48624](https://github.com/rust-lang/rust/pull/48624)).

The regression was introduced when PR #49090 switched macOS from
`std::process::Command` (which uses fork+execvp, correctly using the
child's cwd and PATH) to a custom posix_spawnp-based implementation
(which does not).

## Testing

- `test_bare_program_resolved_via_custom_path` — bare name resolves via
child's custom PATH
- `test_bare_program_with_custom_path_falls_back_when_not_found` —
non-existent binary still errors
- `test_bare_program_with_custom_env_no_path_key` — custom env without
PATH key falls back gracefully
- `test_relative_path_skips_resolution` — relative path resolves against
`current_dir` instead of parent's cwd

Note: The fix and tests are in `darwin.rs` which is macOS-only. The
Linux path uses `smol::process::Command` (via fork+execve) and is
unaffected.

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- Fixed external formatters and language servers failing to launch on
macOS when specified as a bare binary name or relative path and only
available in the project's PATH (e.g. Nix, direnv)

---------

Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>
```

## Parents

- [ef38a4150205dabdeaa34ca05787dda0c3604fd7](https://git.cafe/gitcafe/zed/commit/ef38a4150205dabdeaa34ca05787dda0c3604fd7?format=markdown)

[Source at this commit](https://git.cafe/gitcafe/zed/tree/d1f500edf15b5e6726710ab89199880e75ad45eb?format=markdown)
