diff --git a/scripts/verify-cli-update.test.ts b/scripts/verify-cli-update.test.ts index 0760dd48..c4283bc7 100644 --- a/scripts/verify-cli-update.test.ts +++ b/scripts/verify-cli-update.test.ts @@ -266,6 +266,7 @@ describe("verify-cli-update", () => { it("bootstraps an exact candidate from a legacy v1 update state", async () => { const deps = createDeps(); + let startInstallAttempts = 0; vi.mocked(deps.callWs).mockImplementation(async ({ op }) => { if (op === "updates.getState") { return { version: 1, currentVersion: "0.5.6", updateStatus: "idle" }; @@ -274,6 +275,10 @@ describe("verify-cli-update", () => { return { activity: { hasActiveWork: false } }; } if (op === "updates.startInstall") { + startInstallAttempts += 1; + if (startInstallAttempts === 1) { + throw new Error("update_busy: Update check is already in progress"); + } return { version: 1, currentVersion: "0.5.6", @@ -323,6 +328,8 @@ describe("verify-cli-update", () => { args: { targetVersion: "0.5.7", force: false }, }) ); + expect(deps.wait).toHaveBeenCalledWith(500); + expect(startInstallAttempts).toBe(2); }); it("waits for a just-written candidate dist-tag to propagate", async () => { diff --git a/scripts/verify-cli-update.ts b/scripts/verify-cli-update.ts index 5d0fc41a..12f6090a 100644 --- a/scripts/verify-cli-update.ts +++ b/scripts/verify-cli-update.ts @@ -679,6 +679,37 @@ async function waitForCandidateRelease(input: { ); } +async function startInstallAfterBackgroundCheck(input: { + deps: Pick; + apiUrl: string; + candidateVersion: string; + maxAttempts?: number; +}): Promise { + const maxAttempts = input.maxAttempts ?? 60; + let lastError: unknown; + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + return await input.deps.callWs({ + apiUrl: input.apiUrl, + op: "updates.startInstall", + args: { targetVersion: input.candidateVersion, force: false }, + }); + } catch (startError) { + lastError = startError; + const message = startError instanceof Error ? startError.message : String(startError); + if (!message.startsWith("update_busy: Update check is already in progress")) { + throw startError; + } + if (attempt < maxAttempts) await input.deps.wait(500); + } + } + + throw new Error( + `CLI updater remained busy while waiting for its startup update check: ${lastError instanceof Error ? lastError.message : String(lastError)}` + ); +} + export async function verifyCliUpdate( options: VerifyCliUpdateOptions, deps: VerifyCliUpdateDeps = defaultDeps @@ -767,10 +798,10 @@ export async function verifyCliUpdate( }); if (prepared.activity.hasActiveWork) throw new Error("CLI acceptance prefix unexpectedly has active work"); - const started = await deps.callWs({ + const started = await startInstallAfterBackgroundCheck({ + deps, apiUrl: server.apiUrl, - op: "updates.startInstall", - args: { targetVersion: candidateVersion, force: false }, + candidateVersion, }); if ( started.targetVersion !== candidateVersion ||