Skip to content

Commit f58e681

Browse files
committed
refactor(tailscale): split status retry helper
1 parent c65186f commit f58e681

1 file changed

Lines changed: 2 additions & 50 deletions

File tree

src/infra/tailscale.ts

Lines changed: 2 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Integrates with the local Tailscale CLI for tailnet setup and sharing.
22
import { existsSync } from "node:fs";
3-
import { setTimeout as delay } from "node:timers/promises";
43
import {
54
asDateTimestampMs,
65
resolveExpiresAtMsFromDurationMs,
@@ -13,6 +12,7 @@ import {
1312
import { logVerbose } from "../globals.js";
1413
import { runExec } from "../process/exec.js";
1514
import { toErrorObject } from "./errors.js";
15+
import { readTailscaleStatusJson } from "./tailscale-status.js";
1616

1717
function parsePossiblyNoisyJsonObject(stdout: string): Record<string, unknown> {
1818
const trimmed = stdout.trim();
@@ -24,54 +24,6 @@ function parsePossiblyNoisyJsonObject(stdout: string): Record<string, unknown> {
2424
return JSON.parse(trimmed) as Record<string, unknown>;
2525
}
2626

27-
const TAILSCALE_STATUS_ATTEMPTS = 3;
28-
const TAILSCALE_STATUS_RETRY_DELAY_MS = 500;
29-
30-
function isTransientTailscaleStatusError(error: unknown): boolean {
31-
const record = readRecord(error);
32-
const timedOut = record?.killed === true && record.signal === "SIGTERM";
33-
const detail = [
34-
error instanceof Error ? error.message : undefined,
35-
typeof record?.stderr === "string" ? record.stderr : undefined,
36-
typeof record?.stdout === "string" ? record.stdout : undefined,
37-
]
38-
.filter((value): value is string => Boolean(value))
39-
.join("\n")
40-
.toLowerCase();
41-
42-
return (
43-
timedOut ||
44-
detail.includes("failed to connect to local tailscale") ||
45-
detail.includes("connection refused") ||
46-
detail.includes("503 service unavailable: no backend")
47-
);
48-
}
49-
50-
async function readTailscaleStatusJson(
51-
candidate: string,
52-
exec: typeof runExec,
53-
): Promise<Record<string, unknown>> {
54-
for (let attempt = 1; attempt <= TAILSCALE_STATUS_ATTEMPTS; attempt += 1) {
55-
let stdout: string;
56-
try {
57-
({ stdout } = await exec(candidate, ["status", "--json"], {
58-
timeoutMs: 5000,
59-
maxBuffer: 400_000,
60-
}));
61-
} catch (error) {
62-
if (!isTransientTailscaleStatusError(error) || attempt === TAILSCALE_STATUS_ATTEMPTS) {
63-
throw toErrorObject(error, "Non-Error thrown");
64-
}
65-
// Serve startup can briefly race daemon status. Retry the same detected binary so
66-
// binary fallback does not hide the transient failure or change installations.
67-
await delay(TAILSCALE_STATUS_RETRY_DELAY_MS);
68-
continue;
69-
}
70-
return stdout ? parsePossiblyNoisyJsonObject(stdout) : {};
71-
}
72-
throw new Error("Tailscale status retry loop exhausted");
73-
}
74-
7527
/**
7628
* Locate Tailscale binary using multiple strategies:
7729
* 1. PATH lookup (via which command)
@@ -179,7 +131,7 @@ export async function getTailnetHostname(exec: typeof runExec = runExec, detecte
179131
continue;
180132
}
181133
try {
182-
const parsed = await readTailscaleStatusJson(candidate, exec);
134+
const parsed = await readTailscaleStatusJson(candidate, exec, parsePossiblyNoisyJsonObject);
183135
const self =
184136
typeof parsed.Self === "object" && parsed.Self !== null
185137
? (parsed.Self as Record<string, unknown>)

0 commit comments

Comments
 (0)