{"record":{"id":"f29041d164df8c0e","repo":"decolua/9router","slug":"tailscale-health-check-timed-out-will-retry-via","errorCode":null,"errorMessage":"[Tailscale] health check timed out, will retry via watchdog","messagePattern":"\\[Tailscale\\] health check timed out, will retry via watchdog","errorType":"console","errorClass":null,"httpStatus":null,"severity":"info","filePath":"src/lib/tunnel/tailscale/manager.js","lineNumber":95,"sourceCode":"      stopFunnel();\n      return { success: false, error: \"Tailscale not connected. Device may have been removed. Please re-login.\" };\n    }\n\n    await updateSettings({ tailscaleEnabled: true, tailscaleUrl: result.tunnelUrl });\n    console.log(`[Tailscale] funnel up: ${result.tunnelUrl}`);\n\n    // Provision TLS cert so Funnel can serve HTTPS (non-fatal if fails)\n    const hostname = new URL(result.tunnelUrl).hostname;\n    await provisionCert(hostname);\n\n    // Verify funnel serves /api/health — timeout is non-fatal (DNS may still be propagating)\n    let reachableNow = false;\n    try {\n      await waitForHealth(result.tunnelUrl, token);\n      reachableNow = true;\n    } catch (he) {\n      if (!he.message.startsWith(\"Health check timeout\")) throw he;\n      console.warn(`[Tailscale] health check timed out, will retry via watchdog`);\n    }\n    console.log(`[Tailscale] enable success (reachable=${reachableNow})`);\n    return { success: true, tunnelUrl: result.tunnelUrl };\n  } catch (e) {\n    console.error(`[Tailscale] enable error: ${e.message}`);\n    throw e;\n  } finally {\n    svc.spawnInProgress = false;\n  }\n}\n\nexport async function disableTailscale() {\n  console.log(\"[Tailscale] disable\");\n  svc.cancelToken.cancelled = true;\n  stopFunnel();\n  await updateSettings({ tailscaleEnabled: false, tailscaleUrl: \"\" });\n  return { success: true };\n}","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/tunnel/tailscale/manager.js#L77-L113","documentation":"enableTailscale performs a best-effort health check of the funnel URL after setup. If waitForHealth throws a 'Health check timeout' error, it is downgraded to this warning and enablement still returns success (reachable=false), relying on the funnel watchdog to retry/verify later. Any other health-check error is re-thrown as a real enable failure.","triggerScenarios":"waitForHealth(result.tunnelUrl, token) times out after tailscale funnel is configured: slow cert provisioning, DNS over Tailscale still settling, funnel just started, or MagicDNS name not yet resolvable within the timeout window.","commonSituations":"First-time funnel setup on a fresh machine; tailscaled slow to propagate the funnel; large network latency to the node; cert command just ran; temporary tailscaled restart.","solutions":["Wait — the watchdog retries the health check automatically; verify reachability shortly after.","Verify 'tailscale funnel status' / 'tailscale status' shows the funnel active and the hostname correct.","Check that tailscaled is running and the device is connected to the tailnet.","If permanently unreachable, disable/re-enable the funnel and confirm DNS name and local port are correct."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// confirm tailscale funnel is registered before relying on the URL\nimport { execSync } from \"child_process\";\nconst out = execSync(\"tailscale funnel status\", { encoding: \"utf8\" });\nif (!out.includes(\"https://\")) throw new Error(\"funnel not active yet\");","typeGuard":null,"tryCatchPattern":"// treat only timeout as retryable, mirroring the library\ntry {\n  const res = await enableTailscale();\n} catch (e) {\n  if (e.message.startsWith(\"Health check timeout\")) scheduleRetry();\n  else throw e;\n}","preventionTips":["Ensure tailscaled is running and connected before enabling the funnel.","Allow extra time on first-time funnel setup (cert + DNS settling).","Verify the funnel hostname matches your tailnet DNS name.","Poll the tunnel URL yourself for a minute after enabling instead of trusting immediately."],"tags":["tailscale","funnel","health-check","timeout","tunnel"],"backgroundTag":"health-check-timeout","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}