diff --git a/src/shared/services/cliRuntime.ts b/src/shared/services/cliRuntime.ts index 3bf01d7686..406434cb58 100644 --- a/src/shared/services/cliRuntime.ts +++ b/src/shared/services/cliRuntime.ts @@ -1,4 +1,5 @@ import fs from "fs/promises"; +import fsSync from "fs"; import os from "os"; import path from "path"; import { spawn, execFileSync } from "child_process"; @@ -419,8 +420,13 @@ const getKnownToolPaths = (toolId: string): string[] => { } paths.push(path.join(home, ".local", "bin", posixName)); - paths.push(path.join("/usr", "local", "bin", posixName)); - paths.push(path.join("/usr", "bin", posixName)); + // Only add system paths if they exist (avoids unnecessary stat calls) + if (fsSync.existsSync("/usr/local/bin")) { + paths.push(path.join("/usr", "local", "bin", posixName)); + } + if (fsSync.existsSync("/usr/bin")) { + paths.push(path.join("/usr", "bin", posixName)); + } if (toolId === "opencode") { paths.push(path.join(home, ".opencode", "bin", posixName)); diff --git a/tests/unit/cli-runtime-detection.test.mjs b/tests/unit/cli-runtime-detection.test.mjs index 287bf3d284..edba72c2b7 100644 --- a/tests/unit/cli-runtime-detection.test.mjs +++ b/tests/unit/cli-runtime-detection.test.mjs @@ -61,10 +61,27 @@ describe("Size threshold — checkKnownPath", () => { fs.rmSync(tmpDir, { recursive: true, force: true }); }); - it("should reject files under 30 bytes as suspicious_size", async () => { - const tinyFile = createFile(tmpDir, "tiny-cmd", "a".repeat(20)); - const result = await getCliRuntimeStatus("opencode"); - assert.ok(result.reason !== "suspicious_size" || !result.installed); + it("should detect files >= 30 bytes via env var", async () => { + const prev = process.env.CLI_DROID_BIN; + // Create a valid 30-byte+ script + const content = + process.platform === "win32" + ? "@echo off\r\necho 1.0.0\r\nexit 0\r\n" + : "#!/bin/sh\r\necho 1.0.0\r\nexit 0\r\n"; + const script = createFile(tmpDir, "droid-valid", content); + // Verify it's at least 30 bytes + const stat = fs.statSync(script); + assert.ok(stat.size >= 30, `File should be >= 30 bytes, got ${stat.size}`); + + process.env.CLI_DROID_BIN = script; + try { + const result = await getCliRuntimeStatus("droid"); + assert.ok(result.installed, `Expected installed=true, got reason=${result.reason}`); + assert.ok(result.commandPath === script, `Expected commandPath=${script}`); + } finally { + if (prev !== undefined) process.env.CLI_DROID_BIN = prev; + else delete process.env.CLI_DROID_BIN; + } }); it("should detect a valid CLI script (>= 30 bytes) via env var", async () => {