{"record":{"id":"6d248258756b4dc1","repo":"decolua/9router","slug":"tailscale-not-installed","errorCode":null,"errorMessage":"Tailscale not installed","messagePattern":"Tailscale not installed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/lib/tunnel/tailscale/tailscale.js","lineNumber":743,"sourceCode":"        return;\n      }\n      // Only resolve alreadyLoggedIn if status confirms BackendState=Running\n      if (isTailscaleLoggedIn()) {\n        resolved = true;\n        clearTimeout(timeout);\n        clearInterval(statusPoll);\n        resolve({ alreadyLoggedIn: true });\n        return;\n      }\n      // Otherwise keep polling — daemon may publish AuthURL shortly after exit\n    });\n  });\n}\n\n/** Start tailscale funnel for the given port */\nexport async function startFunnel(port) {\n  const bin = getTailscaleBin();\n  if (!bin) throw new Error(\"Tailscale not installed\");\n\n  // Reset any existing funnel\n  try { execSync(`\"${bin}\" ${SOCKET_FLAG.join(\" \")} funnel --bg reset`, { stdio: \"ignore\", windowsHide: true }); } catch (e) { /* ignore */ }\n\n  return new Promise((resolve, reject) => {\n    const child = spawn(bin, tsArgs(\"funnel\", \"--bg\", `${port}`), {\n      stdio: [\"ignore\", \"pipe\", \"pipe\"],\n      windowsHide: true\n    });\n\n    let resolved = false;\n    let output = \"\";\n\n    const timeout = setTimeout(() => {\n      if (resolved) return;\n      resolved = true;\n      // --bg exits after setup, read actual hostname from status\n      const url = getActualFunnelUrl() || getTailscaleFunnelUrl(port);","sourceCodeStart":725,"sourceCodeEnd":761,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/tunnel/tailscale/tailscale.js#L725-L761","documentation":"startFunnel(port) needs the tailscale CLI binary and resolves it via getTailscaleBin(); when the binary can't be found on the system it throws 'Tailscale not installed'. The tunnel layer refuses to attempt `tailscale funnel` without the executable, so the failure is immediate and deterministic.","triggerScenarios":"Calling startFunnel (or the enableTailscale flow that reaches it) on a machine where tailscale was never installed, was uninstalled, is installed but not on PATH, or where the platform-specific bin lookup (getTailscaleBin) checks a location that doesn't match the actual install (e.g. per-user vs per-machine Windows install, Homebrew vs /usr/local on macOS).","commonSituations":"Fresh machine or container without tailscale; PATH missing the tailscale dir in the server's environment (systemd/daemon environments often have a minimal PATH); tailscale installed after the gateway process started with a cached lookup; Windows install to a non-default directory.","solutions":["Install tailscale (official installer, `winget install tailscale.tailscale`, brew, or the Linux install script) before enabling the funnel.","Confirm `tailscale version` works in the same shell/environment the gateway runs in; if not, fix PATH (e.g. symlink into /usr/local/bin).","Check what getTailscaleBin() searches and point the environment (PATH) at your actual install location.","Restart the gateway process after installing tailscale so binary discovery re-runs.","Use the built-in install flow (installTailscale) instead of expecting a pre-installed binary."],"exampleFix":"// before\nawait startFunnel(20128);\n// after\nimport { getTailscaleBin } from \".../tailscale.js\";\nif (!getTailscaleBin()) {\n  await installTailscale(); // or instruct user to install tailscale\n}\nawait startFunnel(20128);","handlingStrategy":"fallback","validationCode":"import { execSync } from \"child_process\";\nfunction tailscaleAvailable() {\n  try { execSync(\"tailscale version\", { stdio: \"ignore\" }); return true; }\n  catch { return false; }\n}\nif (!tailscaleAvailable()) console.warn(\"tailscale missing — install before enabling funnel\");","typeGuard":"const hasTailscale = (bin) => typeof bin === \"string\" && bin.length > 0;","tryCatchPattern":"try {\n  await startFunnel(port);\n} catch (e) {\n  if (e.message === \"Tailscale not installed\") {\n    await installTailscale();      // fallback: auto-install\n    return startFunnel(port);\n  }\n  throw e;\n}","preventionTips":["Check getTailscaleBin() at startup and show an install prompt if null.","Ensure the gateway's daemon environment has tailscale on PATH (systemd services have minimal PATH).","Restart the gateway after installing tailscale so binary discovery refreshes.","Provision tailscale in Docker images / AMIs used to run the gateway."],"tags":["tailscale","installation","missing-binary","environment"],"backgroundTag":"binary-not-installed","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}