diff --git a/src/hooks/gmail-ops.ts b/src/hooks/gmail-ops.ts index 4aae31ae3..0749abbf7 100644 --- a/src/hooks/gmail-ops.ts +++ b/src/hooks/gmail-ops.ts @@ -132,7 +132,7 @@ export async function runGmailSetup(opts: GmailSetupOptions) { const serveBind = opts.bind ?? DEFAULT_GMAIL_SERVE_BIND; const servePort = opts.port ?? DEFAULT_GMAIL_SERVE_PORT; - const servePath = normalizeServePath(opts.path ?? DEFAULT_GMAIL_SERVE_PATH); + const configuredServePath = opts.path ?? baseConfig.hooks?.gmail?.serve?.path; const includeBody = opts.includeBody ?? true; const maxBytes = opts.maxBytes ?? DEFAULT_GMAIL_MAX_BYTES; @@ -140,7 +140,20 @@ export async function runGmailSetup(opts: GmailSetupOptions) { opts.renewEveryMinutes ?? DEFAULT_GMAIL_RENEW_MINUTES; const tailscaleMode = opts.tailscale ?? "funnel"; - const tailscalePath = normalizeServePath(opts.tailscalePath ?? servePath); + // Tailscale strips the path before proxying; keep a public path while gog + // listens on "/" unless the user explicitly configured a serve path. + const servePath = normalizeServePath( + tailscaleMode !== "off" && !configuredServePath + ? "/" + : configuredServePath ?? DEFAULT_GMAIL_SERVE_PATH, + ); + const tailscalePath = normalizeServePath( + opts.tailscalePath ?? + baseConfig.hooks?.gmail?.tailscale?.path ?? + (tailscaleMode !== "off" + ? configuredServePath ?? DEFAULT_GMAIL_SERVE_PATH + : servePath), + ); await runGcloud(["config", "set", "project", projectId, "--quiet"]); await runGcloud([ diff --git a/src/hooks/gmail.test.ts b/src/hooks/gmail.test.ts index f76052af7..e496d81b4 100644 --- a/src/hooks/gmail.test.ts +++ b/src/hooks/gmail.test.ts @@ -60,4 +60,49 @@ describe("gmail hook config", () => { ); expect(result.ok).toBe(false); }); + + it("defaults serve path to / when tailscale is enabled", () => { + const result = resolveGmailHookRuntimeConfig( + { + hooks: { + token: "hook-token", + gmail: { + account: "clawdbot@gmail.com", + topic: "projects/demo/topics/gog-gmail-watch", + pushToken: "push-token", + tailscale: { mode: "funnel" }, + }, + }, + }, + {}, + ); + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.value.serve.path).toBe("/"); + expect(result.value.tailscale.path).toBe("/gmail-pubsub"); + } + }); + + it("keeps explicit serve path for tailscale when set", () => { + const result = resolveGmailHookRuntimeConfig( + { + hooks: { + token: "hook-token", + gmail: { + account: "clawdbot@gmail.com", + topic: "projects/demo/topics/gog-gmail-watch", + pushToken: "push-token", + serve: { path: "/custom" }, + tailscale: { mode: "funnel" }, + }, + }, + }, + {}, + ); + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.value.serve.path).toBe("/custom"); + expect(result.value.tailscale.path).toBe("/custom"); + } + }); }); diff --git a/src/hooks/gmail.ts b/src/hooks/gmail.ts index 46ab27e53..d11d53ad8 100644 --- a/src/hooks/gmail.ts +++ b/src/hooks/gmail.ts @@ -152,14 +152,25 @@ export function resolveGmailHookRuntimeConfig( servePortRaw > 0 ? Math.floor(servePortRaw) : DEFAULT_GMAIL_SERVE_PORT; - const servePath = normalizeServePath( - overrides.servePath ?? gmail?.serve?.path, - ); + const servePathRaw = overrides.servePath ?? gmail?.serve?.path; + const hasExplicitServePath = + typeof servePathRaw === "string" && servePathRaw.trim().length > 0; const tailscaleMode = overrides.tailscaleMode ?? gmail?.tailscale?.mode ?? "off"; + // When exposing the push endpoint via Tailscale, the public path is stripped + // before proxying; use "/" internally unless the user set a path explicitly. + const servePath = normalizeServePath( + tailscaleMode !== "off" && !hasExplicitServePath ? "/" : servePathRaw, + ); + + const tailscalePathRaw = overrides.tailscalePath ?? gmail?.tailscale?.path; const tailscalePath = normalizeServePath( - overrides.tailscalePath ?? gmail?.tailscale?.path ?? servePath, + tailscaleMode !== "off" && !tailscalePathRaw + ? hasExplicitServePath + ? servePathRaw + : DEFAULT_GMAIL_SERVE_PATH + : tailscalePathRaw ?? servePath, ); return {