From b1a555da137fb172a9b7744907d6ea2622382c3f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 25 Jan 2026 02:49:16 +0000 Subject: [PATCH] fix: skip tailscale dns probe when off --- CHANGELOG.md | 3 +- ...s-writing-models-json-no-env-token.test.ts | 28 ++---------- src/gateway/server-discovery-runtime.ts | 6 ++- src/gateway/server-discovery.test.ts | 45 +++++++++++++++++++ src/gateway/server-discovery.ts | 2 + src/gateway/server.impl.ts | 1 + 6 files changed, 58 insertions(+), 27 deletions(-) create mode 100644 src/gateway/server-discovery.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 281ea1e06..f5707a46d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,6 @@ Docs: https://docs.clawd.bot - BlueBubbles: keep part-index GUIDs in reply tags when short IDs are missing. - Web UI: hide internal `message_id` hints in chat bubbles. - Web UI: show Stop button during active runs, swap back to New session when idle. (#1664) Thanks @ndbroadbent. -- Web UI: keep raw config edits from toggling channel save state; enable save/apply on raw changes only. (#1673) Thanks @Glucksberg. - Heartbeat: normalize target identifiers for consistent routing. - TUI: reload history after gateway reconnect to restore session state. (#1663) - Telegram: use wrapped fetch for long-polling on Node to normalize AbortSignal handling. (#1639) @@ -29,7 +28,7 @@ Docs: https://docs.clawd.bot - Agents: auto-compact on context overflow prompt errors before failing. (#1627) Thanks @rodrigouroz. - Agents: use the active auth profile for auto-compaction recovery. - Models: default missing custom provider fields so minimal configs are accepted. -- Gateway: honor trusted proxy client IPs for local pairing + HTTP checks. (#1654) Thanks @ndbroadbent. +- Gateway: skip Tailscale DNS probing when tailscale.mode is off. (#1671) - Gateway: reduce log noise for late invokes + remote node probes; debounce skills refresh. (#1607) Thanks @petter-b. - macOS: default direct-transport `ws://` URLs to port 18789; document `gateway.remote.transport`. (#1603) Thanks @ngutman. - Voice Call: return stream TwiML for outbound conversation calls on initial Twilio webhook. (#1634) diff --git a/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts b/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts index 221410316..93b7ee628 100644 --- a/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts +++ b/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts @@ -48,29 +48,19 @@ describe("models-config", () => { const previous = process.env.COPILOT_GITHUB_TOKEN; const previousGh = process.env.GH_TOKEN; const previousGithub = process.env.GITHUB_TOKEN; - const previousVenice = process.env.VENICE_API_KEY; const previousKimiCode = process.env.KIMICODE_API_KEY; - const previousKimiCodeAlt = process.env.KIMI_CODE_API_KEY; const previousMinimax = process.env.MINIMAX_API_KEY; const previousMoonshot = process.env.MOONSHOT_API_KEY; const previousSynthetic = process.env.SYNTHETIC_API_KEY; - const previousAwsAccessKey = process.env.AWS_ACCESS_KEY_ID; - const previousAwsSecretKey = process.env.AWS_SECRET_ACCESS_KEY; - const previousAwsProfile = process.env.AWS_PROFILE; - const previousAwsBearer = process.env.AWS_BEARER_TOKEN; + const previousVenice = process.env.VENICE_API_KEY; delete process.env.COPILOT_GITHUB_TOKEN; delete process.env.GH_TOKEN; delete process.env.GITHUB_TOKEN; - delete process.env.VENICE_API_KEY; delete process.env.KIMICODE_API_KEY; - delete process.env.KIMI_CODE_API_KEY; delete process.env.MINIMAX_API_KEY; delete process.env.MOONSHOT_API_KEY; delete process.env.SYNTHETIC_API_KEY; - delete process.env.AWS_ACCESS_KEY_ID; - delete process.env.AWS_SECRET_ACCESS_KEY; - delete process.env.AWS_PROFILE; - delete process.env.AWS_BEARER_TOKEN; + delete process.env.VENICE_API_KEY; try { vi.resetModules(); @@ -93,26 +83,16 @@ describe("models-config", () => { else process.env.GH_TOKEN = previousGh; if (previousGithub === undefined) delete process.env.GITHUB_TOKEN; else process.env.GITHUB_TOKEN = previousGithub; - if (previousVenice === undefined) delete process.env.VENICE_API_KEY; - else process.env.VENICE_API_KEY = previousVenice; if (previousKimiCode === undefined) delete process.env.KIMICODE_API_KEY; else process.env.KIMICODE_API_KEY = previousKimiCode; - if (previousKimiCodeAlt === undefined) delete process.env.KIMI_CODE_API_KEY; - else process.env.KIMI_CODE_API_KEY = previousKimiCodeAlt; if (previousMinimax === undefined) delete process.env.MINIMAX_API_KEY; else process.env.MINIMAX_API_KEY = previousMinimax; if (previousMoonshot === undefined) delete process.env.MOONSHOT_API_KEY; else process.env.MOONSHOT_API_KEY = previousMoonshot; if (previousSynthetic === undefined) delete process.env.SYNTHETIC_API_KEY; else process.env.SYNTHETIC_API_KEY = previousSynthetic; - if (previousAwsAccessKey === undefined) delete process.env.AWS_ACCESS_KEY_ID; - else process.env.AWS_ACCESS_KEY_ID = previousAwsAccessKey; - if (previousAwsSecretKey === undefined) delete process.env.AWS_SECRET_ACCESS_KEY; - else process.env.AWS_SECRET_ACCESS_KEY = previousAwsSecretKey; - if (previousAwsProfile === undefined) delete process.env.AWS_PROFILE; - else process.env.AWS_PROFILE = previousAwsProfile; - if (previousAwsBearer === undefined) delete process.env.AWS_BEARER_TOKEN; - else process.env.AWS_BEARER_TOKEN = previousAwsBearer; + if (previousVenice === undefined) delete process.env.VENICE_API_KEY; + else process.env.VENICE_API_KEY = previousVenice; } }); }); diff --git a/src/gateway/server-discovery-runtime.ts b/src/gateway/server-discovery-runtime.ts index f7d16f3b2..ab1628d1d 100644 --- a/src/gateway/server-discovery-runtime.ts +++ b/src/gateway/server-discovery-runtime.ts @@ -13,6 +13,7 @@ export async function startGatewayDiscovery(params: { gatewayTls?: { enabled: boolean; fingerprintSha256?: string }; canvasPort?: number; wideAreaDiscoveryEnabled: boolean; + tailscaleMode: "off" | "serve" | "funnel"; logDiscovery: { info: (msg: string) => void; warn: (msg: string) => void }; }) { let bonjourStop: (() => Promise) | null = null; @@ -20,8 +21,11 @@ export async function startGatewayDiscovery(params: { process.env.CLAWDBOT_DISABLE_BONJOUR !== "1" && process.env.NODE_ENV !== "test" && !process.env.VITEST; + const tailscaleEnabled = params.tailscaleMode !== "off"; const needsTailnetDns = bonjourEnabled || params.wideAreaDiscoveryEnabled; - const tailnetDns = needsTailnetDns ? await resolveTailnetDnsHint() : undefined; + const tailnetDns = needsTailnetDns + ? await resolveTailnetDnsHint({ enabled: tailscaleEnabled }) + : undefined; const sshPortEnv = process.env.CLAWDBOT_SSH_PORT?.trim(); const sshPortParsed = sshPortEnv ? Number.parseInt(sshPortEnv, 10) : NaN; const sshPort = Number.isFinite(sshPortParsed) && sshPortParsed > 0 ? sshPortParsed : undefined; diff --git a/src/gateway/server-discovery.test.ts b/src/gateway/server-discovery.test.ts new file mode 100644 index 000000000..3b7a62b90 --- /dev/null +++ b/src/gateway/server-discovery.test.ts @@ -0,0 +1,45 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; + +const getTailnetHostname = vi.hoisted(() => vi.fn()); + +vi.mock("../infra/tailscale.js", () => ({ getTailnetHostname })); + +import { resolveTailnetDnsHint } from "./server-discovery.js"; + +describe("resolveTailnetDnsHint", () => { + const prevTailnetDns = { value: undefined as string | undefined }; + + beforeEach(() => { + prevTailnetDns.value = process.env.CLAWDBOT_TAILNET_DNS; + delete process.env.CLAWDBOT_TAILNET_DNS; + getTailnetHostname.mockReset(); + }); + + afterEach(() => { + if (prevTailnetDns.value === undefined) { + delete process.env.CLAWDBOT_TAILNET_DNS; + } else { + process.env.CLAWDBOT_TAILNET_DNS = prevTailnetDns.value; + } + }); + + test("returns env hint when disabled", async () => { + process.env.CLAWDBOT_TAILNET_DNS = "studio.tailnet.ts.net."; + const value = await resolveTailnetDnsHint({ enabled: false }); + expect(value).toBe("studio.tailnet.ts.net"); + expect(getTailnetHostname).not.toHaveBeenCalled(); + }); + + test("skips tailscale lookup when disabled", async () => { + const value = await resolveTailnetDnsHint({ enabled: false }); + expect(value).toBeUndefined(); + expect(getTailnetHostname).not.toHaveBeenCalled(); + }); + + test("uses tailscale lookup when enabled", async () => { + getTailnetHostname.mockResolvedValue("host.tailnet.ts.net"); + const value = await resolveTailnetDnsHint({ enabled: true }); + expect(value).toBe("host.tailnet.ts.net"); + expect(getTailnetHostname).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/gateway/server-discovery.ts b/src/gateway/server-discovery.ts index 6c33c3f94..747c38bf8 100644 --- a/src/gateway/server-discovery.ts +++ b/src/gateway/server-discovery.ts @@ -55,11 +55,13 @@ export function resolveBonjourCliPath(opts: ResolveBonjourCliPathOptions = {}): export async function resolveTailnetDnsHint(opts?: { env?: NodeJS.ProcessEnv; exec?: typeof runExec; + enabled?: boolean; }): Promise { const env = opts?.env ?? process.env; const envRaw = env.CLAWDBOT_TAILNET_DNS?.trim(); const envValue = envRaw && envRaw.length > 0 ? envRaw.replace(/\.$/, "") : ""; if (envValue) return envValue; + if (opts?.enabled === false) return undefined; const exec = opts?.exec ?? diff --git a/src/gateway/server.impl.ts b/src/gateway/server.impl.ts index f9ad41cbc..6ab5d91d9 100644 --- a/src/gateway/server.impl.ts +++ b/src/gateway/server.impl.ts @@ -339,6 +339,7 @@ export async function startGatewayServer( ? { enabled: true, fingerprintSha256: gatewayTls.fingerprintSha256 } : undefined, wideAreaDiscoveryEnabled: cfgAtStart.discovery?.wideArea?.enabled === true, + tailscaleMode, logDiscovery, }); bonjourStop = discovery.bonjourStop;