diff --git a/CHANGELOG.md b/CHANGELOG.md index dfe246fd5..47b6541a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ Docs: https://docs.clawd.bot - Docs: fix gog auth services example to include docs scope. (#1454) Thanks @zerone0x. - Slack: read thread replies for message reads when threadId is provided (replies-only). (#1450) Thanks @rodrigouroz. - macOS: prefer linked channels in gateway summary to avoid false “not linked” status. +- macOS/tests: fix gateway summary lookup after guard unwrap; prevent browser opens during tests unless explicitly enabled. (ECID-1483) - Providers: improve GitHub Copilot integration (enterprise support, base URL, and auth flow alignment). ## 2026.1.21-2 diff --git a/apps/macos/Sources/Clawdbot/DebugSettings.swift b/apps/macos/Sources/Clawdbot/DebugSettings.swift index 51886bc5d..ec313b2a0 100644 --- a/apps/macos/Sources/Clawdbot/DebugSettings.swift +++ b/apps/macos/Sources/Clawdbot/DebugSettings.swift @@ -102,7 +102,9 @@ struct DebugSettings: View { } } - Text("When enabled, Clawdbot won't install or manage \(gatewayLaunchdLabel). It will only attach to an existing Gateway.") + Text( + "When enabled, Clawdbot won't install or manage \(gatewayLaunchdLabel). " + + "It will only attach to an existing Gateway.") .font(.caption) .foregroundStyle(.secondary) diff --git a/apps/macos/Sources/Clawdbot/GatewayProcessManager.swift b/apps/macos/Sources/Clawdbot/GatewayProcessManager.swift index 4dc02593b..60964fa39 100644 --- a/apps/macos/Sources/Clawdbot/GatewayProcessManager.swift +++ b/apps/macos/Sources/Clawdbot/GatewayProcessManager.swift @@ -248,12 +248,11 @@ final class GatewayProcessManager { guard let linkId else { return "port \(port), health probe succeeded, \(instanceText)" } - let linked = linkId.flatMap { snap.channels[$0]?.linked } ?? false - let authAge = linkId.flatMap { snap.channels[$0]?.authAgeMs }.flatMap(msToAge) ?? "unknown age" + let linked = snap.channels[linkId]?.linked ?? false + let authAge = snap.channels[linkId]?.authAgeMs.flatMap(msToAge) ?? "unknown age" let label = - linkId.flatMap { snap.channelLabels?[$0] } ?? - linkId?.capitalized ?? - "channel" + snap.channelLabels?[linkId] ?? + linkId.capitalized let linkText = linked ? "linked" : "not linked" return "port \(port), \(label) \(linkText), auth \(authAge), \(instanceText)" } diff --git a/src/commands/onboard-helpers.test.ts b/src/commands/onboard-helpers.test.ts index 6a8dd21b8..5305b24af 100644 --- a/src/commands/onboard-helpers.test.ts +++ b/src/commands/onboard-helpers.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { openUrl, resolveBrowserOpenCommand, resolveControlUiLinks } from "./onboard-helpers.js"; @@ -21,9 +21,14 @@ vi.mock("../infra/tailnet.js", () => ({ pickPrimaryTailnetIPv4: mocks.pickPrimaryTailnetIPv4, })); +afterEach(() => { + vi.unstubAllEnvs(); +}); + describe("openUrl", () => { it("quotes URLs on win32 so '&' is not treated as cmd separator", async () => { - vi.spyOn(process, "platform", "get").mockReturnValue("win32"); + vi.stubEnv("CLAWDBOT_ALLOW_TEST_BROWSER_OPEN", "1"); + const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32"); const url = "https://accounts.google.com/o/oauth2/v2/auth?client_id=abc&response_type=code&redirect_uri=http%3A%2F%2Flocalhost"; @@ -39,15 +44,18 @@ describe("openUrl", () => { timeoutMs: 5_000, windowsVerbatimArguments: true, }); + + platformSpy.mockRestore(); }); }); describe("resolveBrowserOpenCommand", () => { it("marks win32 commands as quoteUrl=true", async () => { - vi.spyOn(process, "platform", "get").mockReturnValue("win32"); + const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32"); const resolved = await resolveBrowserOpenCommand(); expect(resolved.argv).toEqual(["cmd", "/c", "start", ""]); expect(resolved.quoteUrl).toBe(true); + platformSpy.mockRestore(); }); }); diff --git a/src/commands/onboard-helpers.ts b/src/commands/onboard-helpers.ts index e17ca92de..cdc2b1e9e 100644 --- a/src/commands/onboard-helpers.ts +++ b/src/commands/onboard-helpers.ts @@ -192,6 +192,7 @@ function resolveSshTargetHint(): string { } export async function openUrl(url: string): Promise { + if (shouldSkipBrowserOpenInTests()) return false; const resolved = await resolveBrowserOpenCommand(); if (!resolved.argv) return false; const quoteUrl = resolved.quoteUrl === true; @@ -218,6 +219,7 @@ export async function openUrl(url: string): Promise { } export async function openUrlInBackground(url: string): Promise { + if (shouldSkipBrowserOpenInTests()) return false; if (process.platform !== "darwin") return false; const resolved = await resolveBrowserOpenCommand(); if (!resolved.argv || resolved.command !== "open") return false; @@ -308,6 +310,12 @@ export async function detectBinary(name: string): Promise { } } +function shouldSkipBrowserOpenInTests(): boolean { + if (process.env.CLAWDBOT_ALLOW_TEST_BROWSER_OPEN) return false; + if (process.env.VITEST) return true; + return process.env.NODE_ENV === "test"; +} + export async function probeGatewayReachable(params: { url: string; token?: string;