From fb269adeeadcb33056101e52adef100a6d3e4d20 Mon Sep 17 00:00:00 2001 From: Jeff <158072326+jeffrey701@users.noreply.github.com> Date: Sun, 26 Jul 2026 15:48:33 +0200 Subject: [PATCH] fix(miner): fall back to global config.apiUrl in loopoverApiUrl --- .../lib/github-token-resolution.ts | 16 ++++++++--- .../miner-github-token-resolution.test.ts | 28 +++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/packages/loopover-miner/lib/github-token-resolution.ts b/packages/loopover-miner/lib/github-token-resolution.ts index f98f4f60e4..3a817d6458 100644 --- a/packages/loopover-miner/lib/github-token-resolution.ts +++ b/packages/loopover-miner/lib/github-token-resolution.ts @@ -30,6 +30,9 @@ type LoopoverConfigProfile = { type LoopoverConfig = { activeProfile?: unknown; profiles?: Record; + // #8854: a top-level/global apiUrl, mirroring loopover-mcp's config shape — the fallback the miner's + // hand-copied resolver previously skipped (it read only the per-profile apiUrl). + apiUrl?: unknown; }; const DEFAULT_API_URL = "https://api.loopover.ai"; @@ -85,10 +88,15 @@ function loopoverSessionToken(env: NodeJS.ProcessEnv): string | null { function loopoverApiUrl(env: NodeJS.ProcessEnv): string { if (env.LOOPOVER_API_URL) return env.LOOPOVER_API_URL.replace(/\/+$/, ""); - const profileApiUrl = activeLoopoverProfile(env).apiUrl; - if (typeof profileApiUrl === "string" && profileApiUrl.trim()) { - const normalized = profileApiUrl.replace(/\/+$/, ""); - if (!LEGACY_DEFAULT_API_URLS.has(normalized)) return normalized; + // #8854: mirror loopover-mcp's `activeProfile.apiUrl ?? config.apiUrl ?? default` — try the active profile's + // apiUrl first, THEN the top-level/global config.apiUrl, before the hardcoded default. The miner previously + // read only the profile apiUrl, so a config that set apiUrl globally fell straight to the default. Reuses the + // existing activeLoopoverProfile()/loadLoopoverConfig() readers (no new profile-selection branch here). + for (const candidate of [activeLoopoverProfile(env).apiUrl, loadLoopoverConfig(env).apiUrl]) { + if (typeof candidate === "string" && candidate.trim()) { + const normalized = candidate.replace(/\/+$/, ""); + if (!LEGACY_DEFAULT_API_URLS.has(normalized)) return normalized; + } } return DEFAULT_API_URL; } diff --git a/test/unit/miner-github-token-resolution.test.ts b/test/unit/miner-github-token-resolution.test.ts index 6b35f01805..753c1383a2 100644 --- a/test/unit/miner-github-token-resolution.test.ts +++ b/test/unit/miner-github-token-resolution.test.ts @@ -134,6 +134,34 @@ describe("resolveGitHubToken (#6116)", () => { expect(capturedUrl).toBe("https://api.loopover.ai/v1/auth/github/token"); }); + it("falls back to the top-level/global config.apiUrl when the active profile has none (#8854)", async () => { + dir = mkdtempSync(join(tmpdir(), "loopover-miner-github-token-global-apiurl-")); + // apiUrl set globally (not per-profile); trailing slash proves normalization runs on the global branch too. + writeConfig(dir, { apiUrl: "https://global.example/", profiles: { default: { session: { token: "session-token" } } } }); + let capturedUrl: string | undefined; + const fetchImpl = async (url: string) => { + capturedUrl = url; + return Response.json({ token: "live-token" }); + }; + await resolveGitHubToken(configuredEnv(dir), { fetchImpl }); + expect(capturedUrl).toBe("https://global.example/v1/auth/github/token"); + }); + + it("prefers the active profile's apiUrl over the global config.apiUrl (#8854)", async () => { + dir = mkdtempSync(join(tmpdir(), "loopover-miner-github-token-profile-over-global-")); + writeConfig(dir, { + apiUrl: "https://global.example", + profiles: { default: { apiUrl: "https://profile.example", session: { token: "session-token" } } }, + }); + let capturedUrl: string | undefined; + const fetchImpl = async (url: string) => { + capturedUrl = url; + return Response.json({ token: "live-token" }); + }; + await resolveGitHubToken(configuredEnv(dir), { fetchImpl }); + expect(capturedUrl).toBe("https://profile.example/v1/auth/github/token"); + }); + it("treats a legacy default API URL stored in the profile as absent, falling through to the current default", async () => { dir = mkdtempSync(join(tmpdir(), "loopover-miner-github-token-legacy-url-")); writeConfig(dir, { profiles: { default: { apiUrl: "https://gittensory-api.zeronode.workers.dev", session: { token: "session-token" } } } });