From efd7fd96016942561b1a81099af2e5523a589b45 Mon Sep 17 00:00:00 2001 From: Andro Marces Date: Sun, 26 Jul 2026 12:55:41 +0800 Subject: [PATCH] fix(cli): raise readiness budget, back off progressively, name the cheap check waitForCodor gave up after ~5s effective wait (20 attempts, flat 250ms between them), which is not enough for a cold start on Windows through Task Scheduler + PowerShell + Node + better-sqlite3 binding load. The budget is now ~60s with delay doubling from 250ms to a 1s cap, so a fast start still reports fast and a slow start is not abandoned. The failure message named "the user-service logs" even when the service had started successfully and only the probe window was too short; it now points at the cheap, accurate check instead. Claude-Session: https://claude.ai/code/session_01RjMaDN6VbJP9pdbCY6u3Hk --- packages/cli/src/index.spec.ts | 27 ++++++++++++++++++++++++++- packages/cli/src/setup.ts | 18 +++++++++++++++--- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/index.spec.ts b/packages/cli/src/index.spec.ts index bbd00a49..5dff17ab 100644 --- a/packages/cli/src/index.spec.ts +++ b/packages/cli/src/index.spec.ts @@ -594,13 +594,38 @@ describe('@codor/cli', () => { await once(listener, 'close'); } + const sleeps: number[] = []; + await expect(waitForCodor( + 'http://127.0.0.1:65535', + async () => false, + async (milliseconds) => { sleeps.push(milliseconds); }, + )).rejects.toThrow('if the port is listening, the service started'); + expect(sleeps.length).toBeGreaterThan(19); + expect(sleeps.reduce((total, ms) => total + ms, 0)).toBe(60_000); + }); + + it('backs off progressively and resolves past the old 20-attempt limit', async () => { + let attempts = 0; + await expect(waitForCodor( + 'http://127.0.0.1:65535', + async () => { + attempts += 1; + return attempts >= 25; + }, + async () => {}, + )).resolves.toBeUndefined(); + expect(attempts).toBe(25); + }); + + it('caps readiness backoff at 1s after doubling from 250ms', async () => { const sleeps: number[] = []; await expect(waitForCodor( 'http://127.0.0.1:65535', async () => false, async (milliseconds) => { sleeps.push(milliseconds); }, )).rejects.toThrow('did not become ready'); - expect(sleeps).toEqual(Array.from({ length: 19 }, () => 250)); + expect(sleeps.slice(0, 3)).toEqual([250, 500, 1_000]); + expect(sleeps.slice(2, -1).every((ms) => ms === 1_000)).toBe(true); }); posixHostIt('writes private setup files, runs confirmed host steps, and pairs against the Serve origin', async () => { diff --git a/packages/cli/src/setup.ts b/packages/cli/src/setup.ts index a22437ce..a4389f6a 100644 --- a/packages/cli/src/setup.ts +++ b/packages/cli/src/setup.ts @@ -606,17 +606,29 @@ export async function probeCodorStatus(endpoint: string): Promise { const defaultSleep = async (milliseconds: number): Promise => new Promise((resolveSleep) => setTimeout(resolveSleep, milliseconds)); +const READINESS_BUDGET_MS = 60_000; +const READINESS_INITIAL_DELAY_MS = 250; +const READINESS_MAX_DELAY_MS = 1_000; + // harn:assume setup-verifies-codor-before-creating-pairing-code ref=setup-readiness-and-pairing export async function waitForCodor( endpoint: string, probe: (value: string) => Promise, sleep: (milliseconds: number) => Promise, ): Promise { - for (let attempt = 1; attempt <= 20; attempt += 1) { + let elapsedMs = 0; + let delayMs = READINESS_INITIAL_DELAY_MS; + for (;;) { if (await probe(endpoint)) return; - if (attempt < 20) await sleep(250); + const wait = Math.min(delayMs, READINESS_BUDGET_MS - elapsedMs); + if (wait <= 0) break; + await sleep(wait); + elapsedMs += wait; + delayMs = Math.min(delayMs * 2, READINESS_MAX_DELAY_MS); } - throw new Error(`Codor did not become ready at ${endpoint}; inspect the user-service logs`); + throw new Error( + `Codor did not become ready at ${endpoint}; if the port is listening, the service started — verify with \`codor channels\``, + ); } // harn:end setup-verifies-codor-before-creating-pairing-code