From 2428d8890724ed977ad10dd94c69171f256fe37c Mon Sep 17 00:00:00 2001 From: Colin Mollenhour Date: Fri, 21 Aug 2026 04:58:14 +0000 Subject: [PATCH] fix(opencode): preserve external host during recovery --- packages/web/server/lib/opencode/lifecycle.js | 5 +- .../web/server/lib/opencode/lifecycle.test.js | 50 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/packages/web/server/lib/opencode/lifecycle.js b/packages/web/server/lib/opencode/lifecycle.js index 7f3bd1df..c0215404 100644 --- a/packages/web/server/lib/opencode/lifecycle.js +++ b/packages/web/server/lib/opencode/lifecycle.js @@ -792,11 +792,12 @@ export const createOpenCodeLifecycleRuntime = (deps) => { if (state.isExternalOpenCode) { console.log('Re-probing external OpenCode server...'); - const probePort = state.openCodePort || env.ENV_CONFIGURED_OPENCODE_PORT || 4096; + const probePort = state.openCodePort ?? env.ENV_EFFECTIVE_PORT ?? 4096; const probeOrigin = state.openCodeBaseUrl ?? env.ENV_CONFIGURED_OPENCODE_HOST?.origin; const healthy = await probeExternalOpenCode(probePort, probeOrigin); if (healthy) { console.log(`External OpenCode server on port ${probePort} is healthy`); + state.openCodeBaseUrl = probeOrigin ?? null; setOpenCodePort(probePort); state.isOpenCodeReady = true; state.lastOpenCodeError = null; @@ -875,7 +876,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => { } catch (error) { console.error(`Failed to restart OpenCode: ${error.message}`); state.lastOpenCodeError = error.message; - if (!env.ENV_CONFIGURED_OPENCODE_PORT) { + if (!env.ENV_EFFECTIVE_PORT) { state.openCodePort = null; syncToHmrState(); } diff --git a/packages/web/server/lib/opencode/lifecycle.test.js b/packages/web/server/lib/opencode/lifecycle.test.js index 20d32e56..e1603d08 100644 --- a/packages/web/server/lib/opencode/lifecycle.test.js +++ b/packages/web/server/lib/opencode/lifecycle.test.js @@ -151,6 +151,56 @@ describe('OpenCode lifecycle', () => { expect(terminalEvents).toHaveLength(1); }); + it('recovers an external OPENCODE_HOST connection using its configured endpoint', async () => { + const fetchMock = vi.fn(async () => ({ + ok: true, + json: async () => ({ healthy: true }), + })); + globalThis.fetch = fetchMock; + const runtime = createRuntime({}, { + openCodePort: null, + openCodeBaseUrl: null, + isExternalOpenCode: true, + }, { + ENV_CONFIGURED_OPENCODE_PORT: null, + ENV_CONFIGURED_OPENCODE_HOST: { origin: 'http://seamus:4095', port: 4095 }, + ENV_EFFECTIVE_PORT: 4095, + }); + + await runtime.restartOpenCode(); + + expect(fetchMock).toHaveBeenCalledWith( + 'http://seamus:4095/global/health', + expect.objectContaining({ method: 'GET' }), + ); + expect(runtime.testState.openCodePort).toBe(4095); + expect(runtime.testState.openCodeBaseUrl).toBe('http://seamus:4095'); + expect(runtime.testState.lastOpenCodeError).toBeNull(); + }); + + it('retains the OPENCODE_HOST port after an external re-probe fails', async () => { + globalThis.fetch = vi.fn(async () => ({ + ok: false, + json: async () => null, + })); + const runtime = createRuntime({}, { + openCodePort: 4095, + openCodeBaseUrl: 'http://seamus:4095', + isExternalOpenCode: true, + }, { + ENV_CONFIGURED_OPENCODE_PORT: null, + ENV_CONFIGURED_OPENCODE_HOST: { origin: 'http://seamus:4095', port: 4095 }, + ENV_EFFECTIVE_PORT: 4095, + }); + + await expect(runtime.restartOpenCode()).rejects.toThrow( + 'External OpenCode server on port 4095 is not responding', + ); + + expect(runtime.testState.openCodePort).toBe(4095); + expect(runtime.testState.openCodeBaseUrl).toBe('http://seamus:4095'); + }); + it('warms recently used directories after a successful bootstrap', async () => { const fetchMock = vi.fn(async () => ({ ok: true,