From e7c269b8c97c1d548629af5276c3148cb238d04b Mon Sep 17 00:00:00 2001 From: decolua Date: Wed, 23 Sep 2026 12:12:10 +0700 Subject: [PATCH] fix(tailscale): cap enable-flow health wait at 20s Enable waited out the full 180s HEALTH_CHECK.timeoutMs when funnel URL DNS was unreachable, making the toggle hang ~3 minutes before returning success. waitForHealth now takes an optional timeoutMs; enable uses HEALTH_CHECK.enableTimeoutMs (20s) while watchdog/cloudflare keep 180s. Co-Authored-By: Claude Code --- src/lib/tunnel/tailscale/config.js | 1 + src/lib/tunnel/tailscale/healthCheck.js | 6 +++--- src/lib/tunnel/tailscale/manager.js | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/tunnel/tailscale/config.js b/src/lib/tunnel/tailscale/config.js index 3195ad69..fda18401 100644 --- a/src/lib/tunnel/tailscale/config.js +++ b/src/lib/tunnel/tailscale/config.js @@ -2,6 +2,7 @@ export const HEALTH_CHECK = { intervalMs: 2000, timeoutMs: 180000, + enableTimeoutMs: 20000, // Enable flow waits short; watchdog re-verifies afterwards fetchTimeoutMs: 8000, dnsTimeoutMs: 3000, }; diff --git a/src/lib/tunnel/tailscale/healthCheck.js b/src/lib/tunnel/tailscale/healthCheck.js index 428b351d..91cd7b3b 100644 --- a/src/lib/tunnel/tailscale/healthCheck.js +++ b/src/lib/tunnel/tailscale/healthCheck.js @@ -18,12 +18,12 @@ export async function probeUrlAlive(url) { } } -export async function waitForHealth(url, cancelToken = { cancelled: false }) { +export async function waitForHealth(url, cancelToken = { cancelled: false }, { timeoutMs = HEALTH_CHECK.timeoutMs } = {}) { const start = Date.now(); - while (Date.now() - start < HEALTH_CHECK.timeoutMs) { + while (Date.now() - start < timeoutMs) { if (cancelToken.cancelled) throw new Error("cancelled"); if (await probeUrlAlive(url)) return true; await new Promise((r) => setTimeout(r, HEALTH_CHECK.intervalMs)); } - throw new Error(`Health check timeout after ${HEALTH_CHECK.timeoutMs}ms`); + throw new Error(`Health check timeout after ${timeoutMs}ms`); } diff --git a/src/lib/tunnel/tailscale/manager.js b/src/lib/tunnel/tailscale/manager.js index 7fe40e7e..06cef971 100644 --- a/src/lib/tunnel/tailscale/manager.js +++ b/src/lib/tunnel/tailscale/manager.js @@ -1,6 +1,7 @@ import { loadState, generateShortId } from "../shared/state.js"; import { startFunnel, stopFunnel, isTailscaleRunning, isTailscaleRunningStrict, isTailscaleLoggedIn, isTailscaleLoggedInStrict, startLogin, startDaemonWithPassword, provisionCert } from "./tailscale.js"; import { waitForHealth } from "./healthCheck.js"; +import { HEALTH_CHECK } from "./config.js"; import { getSettings, updateSettings } from "@/lib/localDb"; import { getCachedPassword, loadEncryptedPassword, initDbHooks } from "@/mitm/manager"; @@ -88,7 +89,7 @@ export async function enableTailscale(localPort = 20128) { // Verify funnel serves /api/health — timeout is non-fatal (DNS may still be propagating) let reachableNow = false; try { - await waitForHealth(result.tunnelUrl, token); + await waitForHealth(result.tunnelUrl, token, { timeoutMs: HEALTH_CHECK.enableTimeoutMs }); reachableNow = true; } catch (he) { if (!he.message.startsWith("Health check timeout")) throw he;