From 4cbee27383819972602f7c353f241e94ca1a8c81 Mon Sep 17 00:00:00 2001 From: Isaac Sanchez-Hawkins <266845420+isanchez404@users.noreply.github.com> Date: Tue, 12 May 2026 04:00:31 -0400 Subject: [PATCH] fix(opencode): clear readiness probe timers (#1226) Co-authored-by: Isaac Sanchez --- .../server/lib/opencode/network-runtime.js | 8 +++- .../lib/opencode/network-runtime.test.js | 37 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 packages/web/server/lib/opencode/network-runtime.test.js diff --git a/packages/web/server/lib/opencode/network-runtime.js b/packages/web/server/lib/opencode/network-runtime.js index 7ca7874e..a9094e46 100644 --- a/packages/web/server/lib/opencode/network-runtime.js +++ b/packages/web/server/lib/opencode/network-runtime.js @@ -29,9 +29,10 @@ export const createOpenCodeNetworkRuntime = (deps) => { const waitForReady = async (url, timeoutMs = 10000) => { const start = Date.now(); while (Date.now() - start < timeoutMs) { + let timeout = null; try { const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 3000); + timeout = setTimeout(() => controller.abort(), 3000); const response = await fetch(`${url.replace(/\/+$/, '')}/global/health`, { method: 'GET', headers: { @@ -41,6 +42,7 @@ export const createOpenCodeNetworkRuntime = (deps) => { signal: controller.signal, }); clearTimeout(timeout); + timeout = null; if (response.ok) { const body = await response.json().catch(() => null); @@ -49,6 +51,10 @@ export const createOpenCodeNetworkRuntime = (deps) => { } } } catch { + } finally { + if (timeout) { + clearTimeout(timeout); + } } await new Promise((resolve) => setTimeout(resolve, 100)); } diff --git a/packages/web/server/lib/opencode/network-runtime.test.js b/packages/web/server/lib/opencode/network-runtime.test.js new file mode 100644 index 00000000..0423f145 --- /dev/null +++ b/packages/web/server/lib/opencode/network-runtime.test.js @@ -0,0 +1,37 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { createOpenCodeNetworkRuntime } from './network-runtime.js'; + +const createRuntime = () => createOpenCodeNetworkRuntime({ + state: { + openCodePort: 4096, + openCodeBaseUrl: null, + openCodeApiPrefix: '', + openCodeApiPrefixDetected: false, + openCodeApiDetectionTimer: null, + }, + getOpenCodeAuthHeaders: () => ({}), +}); + +describe('OpenCode network runtime', () => { + afterEach(() => { + vi.useRealTimers(); + vi.unstubAllGlobals(); + }); + + it('clears the probe abort timer when readiness fetch rejects', async () => { + vi.useFakeTimers(); + vi.setSystemTime(0); + vi.stubGlobal('fetch', vi.fn(async () => { + throw new Error('offline'); + })); + + const runtime = createRuntime(); + const readyPromise = runtime.waitForReady('http://127.0.0.1:4096', 1); + + await vi.advanceTimersByTimeAsync(100); + await expect(readyPromise).resolves.toBe(false); + + expect(vi.getTimerCount()).toBe(0); + }); +});