diff --git a/extensions/voice-call/src/webhook/tailscale.test.ts b/extensions/voice-call/src/webhook/tailscale.test.ts index e42bc5cafa1..55e6b786972 100644 --- a/extensions/voice-call/src/webhook/tailscale.test.ts +++ b/extensions/voice-call/src/webhook/tailscale.test.ts @@ -40,6 +40,19 @@ function createProc(params?: { code?: number; stdout?: string }) { return proc; } +function createErrorProc() { + const proc = new EventEmitter() as EventEmitter & { + stdout: EventEmitter; + kill: ReturnType; + }; + proc.stdout = new EventEmitter(); + proc.kill = vi.fn(); + setTimeout(() => { + proc.emit("error", Object.assign(new Error("spawn tailscale ENOENT"), { code: "ENOENT" })); + }, 0); + return proc; +} + describe("voice-call tailscale helpers", () => { beforeEach(() => { vi.clearAllMocks(); @@ -83,6 +96,12 @@ describe("voice-call tailscale helpers", () => { await expect(getTailscaleSelfInfo()).resolves.toBeNull(); }); + it("treats missing tailscale binary as unavailable instead of leaking spawn errors", async () => { + spawnMock.mockReturnValueOnce(createErrorProc()); + + await expect(getTailscaleSelfInfo()).resolves.toBeNull(); + }); + it("sets up and cleans up exposure routes with the selected mode", async () => { spawnMock .mockReturnValueOnce( diff --git a/extensions/voice-call/src/webhook/tailscale.ts b/extensions/voice-call/src/webhook/tailscale.ts index d0051fbcb53..03717ad932b 100644 --- a/extensions/voice-call/src/webhook/tailscale.ts +++ b/extensions/voice-call/src/webhook/tailscale.ts @@ -16,18 +16,32 @@ function runTailscaleCommand( }); let stdout = ""; + let settled = false; + let timer: ReturnType; + const finish = (result: { code: number; stdout: string }) => { + if (settled) { + return; + } + settled = true; + clearTimeout(timer); + resolve(result); + }; + proc.stdout.on("data", (data) => { stdout += data; }); - const timer = setTimeout(() => { + timer = setTimeout(() => { proc.kill("SIGKILL"); - resolve({ code: -1, stdout: "" }); + finish({ code: -1, stdout: "" }); }, timeoutMs); + proc.on("error", () => { + finish({ code: -1, stdout: "" }); + }); + proc.on("close", (code) => { - clearTimeout(timer); - resolve({ code: code ?? -1, stdout }); + finish({ code: code ?? -1, stdout }); }); }); }