From 81e53202f25f216ddfd795cc28bdd06c856d9de1 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 26 Jun 2026 07:51:07 +0100 Subject: [PATCH] fix(scripts): bypass gh wrapper shims --- scripts/lib/plain-gh.mjs | 15 +++++++++++++-- scripts/lib/plain-gh.sh | 14 ++++++++++++-- test/scripts/plain-gh.test.ts | 20 +++++++++++++++++++- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/scripts/lib/plain-gh.mjs b/scripts/lib/plain-gh.mjs index 23e65db9e462..0adc4503a934 100644 --- a/scripts/lib/plain-gh.mjs +++ b/scripts/lib/plain-gh.mjs @@ -3,6 +3,14 @@ import fs from "node:fs"; import path from "node:path"; export const PLAIN_GH_MAX_BUFFER_BYTES = 32 * 1024 * 1024; +export const PLAIN_GH_SYSTEM_CANDIDATES = [ + // Prefer package-manager opt paths: bin/gh may intentionally be an Octopool shim. + "/opt/homebrew/opt/gh/bin/gh", + "/usr/local/opt/gh/bin/gh", + "/home/linuxbrew/.linuxbrew/opt/gh/bin/gh", + "/opt/homebrew/bin/gh", + "/usr/local/bin/gh", +]; function isExecutable(filePath) { try { @@ -32,7 +40,10 @@ export function plainGhEnv(env = process.env) { return next; } -export function resolvePlainGhBin(env = process.env) { +export function resolvePlainGhBin( + env = process.env, + systemCandidates = PLAIN_GH_SYSTEM_CANDIDATES, +) { if (env.OPENCLAW_GH_BIN) { if (isExecutable(env.OPENCLAW_GH_BIN)) { return env.OPENCLAW_GH_BIN; @@ -40,7 +51,7 @@ export function resolvePlainGhBin(env = process.env) { throw new Error(`OPENCLAW_GH_BIN is not executable: ${env.OPENCLAW_GH_BIN}`); } - for (const candidate of ["/opt/homebrew/bin/gh", "/usr/local/bin/gh"]) { + for (const candidate of systemCandidates) { if (isExecutable(candidate)) { return candidate; } diff --git a/scripts/lib/plain-gh.sh b/scripts/lib/plain-gh.sh index 232ad760757e..f18742eb784f 100644 --- a/scripts/lib/plain-gh.sh +++ b/scripts/lib/plain-gh.sh @@ -24,12 +24,12 @@ resolve_plain_gh_bin() { fi local candidate - for candidate in /opt/homebrew/bin/gh /usr/local/bin/gh; do + while IFS= read -r candidate; do if [ -x "$candidate" ]; then printf '%s\n' "$candidate" return 0 fi - done + done < <(plain_gh_system_candidates) if candidate=$(PATH="$(plain_gh_search_path)" type -P gh 2>/dev/null); then printf '%s\n' "$candidate" @@ -39,6 +39,16 @@ resolve_plain_gh_bin() { type -P gh 2>/dev/null } +plain_gh_system_candidates() { + # bin/gh may intentionally be an Octopool shim; prefer package-manager opt paths. + printf '%s\n' \ + /opt/homebrew/opt/gh/bin/gh \ + /usr/local/opt/gh/bin/gh \ + /home/linuxbrew/.linuxbrew/opt/gh/bin/gh \ + /opt/homebrew/bin/gh \ + /usr/local/bin/gh +} + plain_gh_search_path() { local path_value="${PATH:-}" local home_bin="${HOME:-}/bin" diff --git a/test/scripts/plain-gh.test.ts b/test/scripts/plain-gh.test.ts index e7520342c108..7e918ea81994 100644 --- a/test/scripts/plain-gh.test.ts +++ b/test/scripts/plain-gh.test.ts @@ -4,7 +4,12 @@ import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; -import { execPlainGh, plainGhEnv, resolvePlainGhBin } from "../../scripts/lib/plain-gh.mjs"; +import { + execPlainGh, + plainGhEnv, + PLAIN_GH_SYSTEM_CANDIDATES, + resolvePlainGhBin, +} from "../../scripts/lib/plain-gh.mjs"; const tempDirs: string[] = []; @@ -64,6 +69,16 @@ describe("plain gh helpers", () => { ).toBe(ghPath); }); + it("prefers package-manager gh paths over bin shims", () => { + const realGh = makeFakeGh(); + const shimGh = makeFakeGh(); + + expect(resolvePlainGhBin({ PATH: shimGh }, [realGh, shimGh])).toBe(realGh); + expect(PLAIN_GH_SYSTEM_CANDIDATES.indexOf("/opt/homebrew/opt/gh/bin/gh")).toBeLessThan( + PLAIN_GH_SYSTEM_CANDIDATES.indexOf("/opt/homebrew/bin/gh"), + ); + }); + it("normalizes color environment for JSON-safe gh output", () => { expect( plainGhEnv({ @@ -135,5 +150,8 @@ describe("plain gh helpers", () => { expect(helper).toContain("type -P gh"); expect(helper).not.toContain("command -v gh"); + expect(helper.indexOf("/opt/homebrew/opt/gh/bin/gh")).toBeLessThan( + helper.indexOf("/opt/homebrew/bin/gh"), + ); }); });