Skip to content

Commit 4dfde9e

Browse files
committed
fix(platform): surface Nix-managed agent CLI install paths
The packaged macOS (and Linux) Desktop app did not discover agent CLIs installed through nix-darwin `environment.systemPackages` or per-user `nix profile install`. After launching from Finder and rescanning, Codex / Claude Code / Gemini resolved cleanly from a terminal but were reported as not found on `PATH`. Root cause: in a GUI-launched Electron process the inherited `PATH` is minimal (launchd does not source the user's shell rc), so discovery relies on `wellKnownUserToolchainBins` in `packages/platform/src/toolchain.ts`. The helper's well-known list covered Homebrew, npm/nvm/fnm/mise/asdf, Deno, Go, pyenv, Scoop, Volta, Vite+, Kimi, Bun, Cargo and the system Homebrew locations, but did not include the standard Nix install roots: - `/run/current-system/sw/bin` (NixOS / nix-darwin system profile) - `~/.nix-profile/bin` (per-user Nix profile) Both are documented well-known paths in Nix documentation and are the canonical locations CLIs installed via `environment.systemPackages` or `nix profile install` end up in. Changes: - `packages/platform/src/toolchain.ts`: - Add `~/.nix-profile/bin` to the user-level CLI install list (alongside Deno / Go / pyenv) so single-user Nix installs are surfaced the same way Homebrew/etc are. Best-effort — a missing dir contributes nothing; the existing `<dir>/bin` join behaviour is preserved. - Add `/run/current-system/sw/bin` to the `includeSystemBins`-gated block, next to `/opt/homebrew/bin` and `/usr/local/bin`. Same rationale: a NixOS / nix-darwin system-installed CLI lives here, and a GUI-launched daemon needs the explicit probe to find it. - `packages/platform/tests/index.test.ts`: - Extend the existing "Deno, Go, pyenv" user-level test to also assert `~/.nix-profile/bin` is returned. - Extend both `includeSystemBins` true/false tests to also assert `/run/current-system/sw/bin` is included / omitted accordingly. Two assertions in two existing tests, no new test cases to keep the file dense. Verified: `pnpm --filter @open-design/platform test` passes 76/76 tests, `pnpm --filter @open-design/platform typecheck` clean. Fixes #6121 Signed-off-by: xxiaoxiong <2482929840@qq.com>
1 parent 6b90486 commit 4dfde9e

2 files changed

Lines changed: 24 additions & 8 deletions

File tree

packages/platform/src/toolchain.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,15 @@ export function wellKnownUserToolchainBins(
135135
join(home, ".npm-packages", "bin"),
136136
// Other common user-level toolchains that install CLI shims outside the
137137
// Node ecosystem but still ship agent CLIs (or their dependencies):
138-
// Deno's install root, Go's default GOBIN, and pyenv's shim dir. All are
139-
// best-effort — a missing dir contributes nothing.
138+
// Deno's install root, Go's default GOBIN, pyenv's shim dir, and the
139+
// per-user Nix profile. All are best-effort — a missing dir contributes
140+
// nothing. The Nix profile covers `nix profile install`'d CLIs for hosts
141+
// (single-user installs or HOME-managed profiles) without
142+
// /run/current-system/sw/bin.
140143
join(home, ".deno", "bin"),
141144
join(home, "go", "bin"),
142145
join(home, ".pyenv", "shims"),
146+
join(home, ".nix-profile", "bin"),
143147
);
144148

145149
// Windows-only user install roots that GUI launches miss. Scoop drops
@@ -167,7 +171,15 @@ export function wellKnownUserToolchainBins(
167171
}
168172

169173
if (includeSystemBins) {
170-
dirs.push("/opt/homebrew/bin", "/usr/local/bin");
174+
dirs.push(
175+
"/opt/homebrew/bin",
176+
"/usr/local/bin",
177+
// NixOS and nix-darwin expose system-installed CLIs here.
178+
// GUI-launched daemons inherit a minimal PATH that omits
179+
// this path even though user-installed agent CLIs (codex,
180+
// claude, gemini, …) live here.
181+
"/run/current-system/sw/bin",
182+
);
171183
}
172184
// Per-version Node toolchains: scan the install root and surface every
173185
// version directory's bin folder. Best-effort — missing roots simply

packages/platform/tests/index.test.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -805,15 +805,17 @@ describe("wellKnownUserToolchainBins", () => {
805805
});
806806

807807
// Non-Node user toolchains that still ship agent CLIs (or their deps):
808-
// Deno's install root, Go's default GOBIN, and pyenv's shim dir. GUI
809-
// launches inherit a stripped PATH, so these must be searched explicitly.
810-
it("includes Deno, Go, and pyenv user toolchain dirs", () => {
808+
// Deno's install root, Go's default GOBIN, pyenv's shim dir, and the
809+
// per-user Nix profile. GUI launches inherit a stripped PATH, so these
810+
// must be searched explicitly.
811+
it("includes Deno, Go, pyenv, and ~/.nix-profile/bin user toolchain dirs", () => {
811812
const home = mkdtempSync(join(tmpdir(), "wkutb-extra-"));
812813
try {
813814
const dirs = wellKnownUserToolchainBins({ home, env: {}, includeSystemBins: false });
814815
expect(dirs).toContain(join(home, ".deno", "bin"));
815816
expect(dirs).toContain(join(home, "go", "bin"));
816817
expect(dirs).toContain(join(home, ".pyenv", "shims"));
818+
expect(dirs).toContain(join(home, ".nix-profile", "bin"));
817819
} finally {
818820
rmSync(home, { recursive: true, force: true });
819821
}
@@ -1112,23 +1114,25 @@ describe("wellKnownUserToolchainBins", () => {
11121114
}
11131115
});
11141116

1115-
it("includes /opt/homebrew/bin and /usr/local/bin when includeSystemBins is true", () => {
1117+
it("includes /opt/homebrew/bin and /usr/local/bin and /run/current-system/sw/bin when includeSystemBins is true", () => {
11161118
const home = mkdtempSync(join(tmpdir(), "wkutb-sys-"));
11171119
try {
11181120
const dirs = wellKnownUserToolchainBins({ home, env: {}, includeSystemBins: true });
11191121
expect(dirs).toContain("/opt/homebrew/bin");
11201122
expect(dirs).toContain("/usr/local/bin");
1123+
expect(dirs).toContain("/run/current-system/sw/bin");
11211124
} finally {
11221125
rmSync(home, { recursive: true, force: true });
11231126
}
11241127
});
11251128

1126-
it("omits /opt/homebrew/bin and /usr/local/bin when includeSystemBins is false", () => {
1129+
it("omits /opt/homebrew/bin, /usr/local/bin, and /run/current-system/sw/bin when includeSystemBins is false", () => {
11271130
const home = mkdtempSync(join(tmpdir(), "wkutb-nosys-"));
11281131
try {
11291132
const dirs = wellKnownUserToolchainBins({ home, env: {}, includeSystemBins: false });
11301133
expect(dirs).not.toContain("/opt/homebrew/bin");
11311134
expect(dirs).not.toContain("/usr/local/bin");
1135+
expect(dirs).not.toContain("/run/current-system/sw/bin");
11321136
} finally {
11331137
rmSync(home, { recursive: true, force: true });
11341138
}

0 commit comments

Comments
 (0)