diff --git a/src/cli/update-cli.test.ts b/src/cli/update-cli.test.ts index 2666369ba9f..af00ab2996f 100644 --- a/src/cli/update-cli.test.ts +++ b/src/cli/update-cli.test.ts @@ -479,6 +479,31 @@ describe("update-cli", () => { expect(runDaemonRestart).not.toHaveBeenCalled(); }); + it("keeps downgrade post-update work in the current process", async () => { + setupUpdatedRootRefresh({ + gatewayUpdateImpl: async () => + makeOkUpdateResult({ + mode: "npm", + root: createCaseDir("openclaw-downgraded-root"), + before: { version: "2026.4.14" }, + after: { version: "2026.4.10" }, + }), + }); + readPackageVersion.mockResolvedValue("2026.4.14"); + vi.mocked(resolveNpmChannelTag).mockResolvedValue({ + tag: "latest", + version: "2026.4.10", + }); + + await updateCommand({ yes: true, tag: "2026.4.10" }); + + expect(spawn).not.toHaveBeenCalled(); + expect(syncPluginsForUpdateChannel).toHaveBeenCalled(); + expect(updateNpmInstalledPlugins).toHaveBeenCalled(); + expect(runDaemonInstall).toHaveBeenCalled(); + expect(defaultRuntime.exit).not.toHaveBeenCalledWith(1); + }); + it("fails the update when the fresh process exits non-zero", async () => { setupUpdatedRootRefresh(); spawn.mockImplementationOnce(() => { diff --git a/src/cli/update-cli/update-command.ts b/src/cli/update-cli/update-command.ts index 6eb79fbac7f..4ca9e958e8f 100644 --- a/src/cli/update-cli/update-command.ts +++ b/src/cli/update-cli/update-command.ts @@ -814,6 +814,13 @@ async function continuePostCoreUpdateInFreshProcess(params: { return true; } +function shouldResumePostCoreUpdateInFreshProcess(params: { + result: UpdateRunResult; + downgradeRisk: boolean; +}): boolean { + return isPackageManagerUpdateMode(params.result.mode) && !params.downgradeRisk; +} + export async function updateCommand(opts: UpdateCommandOptions): Promise { suppressDeprecations(); const invocationCwd = tryResolveInvocationCwd(); @@ -1149,7 +1156,12 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise { const postUpdateRoot = result.root ?? root; let pluginsUpdatedInFreshProcess = false; - if (isPackageManagerUpdateMode(result.mode)) { + if ( + shouldResumePostCoreUpdateInFreshProcess({ + result, + downgradeRisk, + }) + ) { pluginsUpdatedInFreshProcess = await continuePostCoreUpdateInFreshProcess({ root: postUpdateRoot, channel,