From f013e0071a187411c6f0bcb6ec1af37fac021255 Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Tue, 12 May 2026 10:26:01 +0200 Subject: [PATCH] fix(cli): retry daemon health before startup failure (#17) Co-authored-by: Andrey Avtomonov <7889985+andreybavt@users.noreply.github.com> --- .../cli/src/managed-python-daemon.test.ts | 35 +++++++++++++++++++ packages/cli/src/managed-python-daemon.ts | 9 +++++ 2 files changed, 44 insertions(+) diff --git a/packages/cli/src/managed-python-daemon.test.ts b/packages/cli/src/managed-python-daemon.test.ts index 24df2a78..4e7af22c 100644 --- a/packages/cli/src/managed-python-daemon.test.ts +++ b/packages/cli/src/managed-python-daemon.test.ts @@ -170,6 +170,41 @@ describe('managed Python daemon lifecycle', () => { }); }); + it('makes a final health probe before reporting startup failure', async () => { + const spawnDaemon = makeSpawn(5556); + const installRuntime = vi.fn(async () => installResult(tempDir)); + const fetch = vi + .fn() + .mockRejectedValueOnce(new Error('fetch failed')) + .mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => ({ status: 'healthy', version: '0.2.0' }), + text: async () => '', + }); + + const result = await startManagedPythonDaemon({ + cliVersion: '0.2.0', + runtimeRoot: join(tempDir, 'runtime'), + features: ['core'], + installRuntime, + spawnDaemon, + fetch, + allocatePort: vi.fn(async () => 61234), + now: () => new Date('2026-05-11T00:00:00.000Z'), + startupTimeoutMs: 5, + pollIntervalMs: 20, + }); + + expect(result.status).toBe('started'); + expect(fetch).toHaveBeenCalledTimes(2); + expect(JSON.parse(await readFile(layout(tempDir).daemonStatePath, 'utf8'))).toMatchObject({ + pid: 5556, + port: 61234, + version: '0.2.0', + }); + }); + it('reuses a healthy daemon with the requested feature set', async () => { await mkdir(layout(tempDir).versionDir, { recursive: true }); await writeFile(layout(tempDir).daemonStatePath, `${JSON.stringify(runningState(tempDir), null, 2)}\n`); diff --git a/packages/cli/src/managed-python-daemon.ts b/packages/cli/src/managed-python-daemon.ts index 4b128c63..2caf9182 100644 --- a/packages/cli/src/managed-python-daemon.ts +++ b/packages/cli/src/managed-python-daemon.ts @@ -273,6 +273,15 @@ async function waitForHealth(input: { lastDetail = health.detail; await delay(input.pollIntervalMs); } + const finalHealth = await healthOk({ + state: input.state, + cliVersion: input.cliVersion, + fetch: input.fetch, + }); + if (finalHealth.ok) { + return; + } + lastDetail = finalHealth.detail; throw new Error(`KTX Python daemon failed to start: ${lastDetail}. stderr: ${input.state.stderrLog}`); }