{"record":{"id":"cddc314e7d674de2","repo":"louislam/uptime-kuma","slug":"error-checking-tailscale-ping-err","errorCode":null,"errorMessage":"Error checking Tailscale ping: ${err}","messagePattern":"Error checking Tailscale ping: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/monitor-types/tailscale-ping.js","lineNumber":17,"sourceCode":"const { MonitorType } = require(\"./monitor-type\");\nconst { UP } = require(\"../../src/util\");\nconst childProcessAsync = require(\"promisify-child-process\");\n\nclass TailscalePing extends MonitorType {\n    name = \"tailscale-ping\";\n\n    /**\n     * @inheritdoc\n     */\n    async check(monitor, heartbeat, _server) {\n        try {\n            let tailscaleOutput = await this.runTailscalePing(monitor.hostname, monitor.interval);\n            this.parseTailscaleOutput(tailscaleOutput, heartbeat);\n        } catch (err) {\n            // trigger log function somewhere to display a notification or alert to the user (but how?)\n            throw new Error(`Error checking Tailscale ping: ${err}`);\n        }\n    }\n\n    /**\n     * Runs the Tailscale ping command to the given URL.\n     * @param {string} hostname The hostname to ping.\n     * @param {number} interval Interval to send ping\n     * @returns {Promise<string>} A Promise that resolves to the output of the Tailscale ping command\n     * @throws Will throw an error if the command execution encounters any error.\n     */\n    async runTailscalePing(hostname, interval) {\n        let timeout = interval * 1000 * 0.8;\n        let res = await childProcessAsync.spawn(\"tailscale\", [\"ping\", \"--c\", \"1\", hostname], {\n            timeout: timeout,\n            encoding: \"utf8\",\n        });\n        if (res.stderr && res.stderr.toString() && res.code !== 0) {\n            throw new Error(`Error in output: ${res.stderr.toString()}`);","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/louislam/uptime-kuma/blob/6b5ea0155793e666666745fb8d6fef1e829543a2/server/monitor-types/tailscale-ping.js#L1-L35","documentation":"Top-level catch-all inside TailscalePing.check(). It wraps any error thrown by runTailscalePing() (spawn failure, stderr output, empty output) or parseTailscaleOutput() (timed out, ACL, unexpected line) into a single 'Error checking Tailscale ping' message that carries the original cause in its string. The original error is concatenated rather than chained via .cause, so structured detail is lost.","triggerScenarios":"Produced for ANY failure during a tailscale-ping monitor cycle: 'tailscale' binary not found, spawn ENOENT, command timeout, non-zero exit with stderr, empty stdout, or any unrecognised line in the parsed output.","commonSituations":"Tailscale CLI not installed on the Uptime-Kuma host; tailscaled not running or not logged in; ACLs blocking the target; the monitor hostname is the machine's own Tailscale IP; the target node is offline; the installed tailscale version emits output the parser does not recognise.","solutions":["Read the inner error text to identify which specific failure occurred (ENOENT, timeout, 'no matching peer', etc.) and address that root cause.","Ensure the 'tailscale' binary is installed and on PATH for the Uptime-Kuma process.","Confirm tailscaled is running and logged in: 'tailscale status'.","If migrating to structured errors, wrap with `new Error(msg, { cause: err })` so the original is inspectable."],"exampleFix":"// before\n} catch (err) {\n    throw new Error(`Error checking Tailscale ping: ${err}`);\n}\n\n// after — preserve the cause chain for diagnostics\n} catch (err) {\n    throw new Error(`Error checking Tailscale ping: ${err.message}`, { cause: err });\n}","handlingStrategy":"try-catch","validationCode":"const fs = require(\"fs\");\nfunction tailscaleAvailable() {\n  try { require(\"child_process\").execFileSync(\"tailscale\", [\"--version\"], { stdio: \"ignore\" }); return true; }\n  catch { return false; }\n}","typeGuard":"function isTailscaleError(e) { return /Tailscale/.test(e?.message || \"\"); }","tryCatchPattern":"try { ... }\ncatch (err) {\n  heartbeat.status = DOWN;\n  heartbeat.msg = `Error checking Tailscale ping: ${err.message}`;\n  // throw new Error(..., { cause: err }) if re-throwing\n}","preventionTips":["Ensure the tailscale CLI is installed and tailscaled is logged in.","Use { cause } when wrapping to preserve diagnostics.","Surface the inner error message to the user instead of only the generic wrapper."],"tags":["tailscale","error-wrapping","vpn","spawn"],"backgroundTag":null,"analyzedSha":"6b5ea0155793e666666745fb8d6fef1e829543a2","analyzedAt":"2026-08-12T23:42:12.959Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}