diff --git a/src/cli/update-cli/post-core-plugin-convergence.test.ts b/src/cli/update-cli/post-core-plugin-convergence.test.ts index dea567a3a53f..a8331420dfd4 100644 --- a/src/cli/update-cli/post-core-plugin-convergence.test.ts +++ b/src/cli/update-cli/post-core-plugin-convergence.test.ts @@ -154,6 +154,22 @@ describe("runPostCorePluginConvergence", () => { }); }); + it("uses an explicit compatibility host version for startup convergence", async () => { + const cfg = { plugins: { entries: {} } } as unknown as OpenClawConfig; + await runPostCorePluginConvergence({ + cfg, + env: { OPENCLAW_COMPATIBILITY_HOST_VERSION: "2026.5.12" }, + compatibilityHostVersion: "2026.7.2-beta.7", + }); + expect(mocks.repairMissingConfiguredPluginInstalls).toHaveBeenCalledWith({ + cfg, + env: { + OPENCLAW_COMPATIBILITY_HOST_VERSION: "2026.7.2-beta.7", + OPENCLAW_UPDATE_POST_CORE_CONVERGENCE: "1", + }, + }); + }); + it("returns ok when no warnings/failures and includes repair changes", async () => { mocks.repairMissingConfiguredPluginInstalls.mockResolvedValue({ changes: ['Repaired missing configured plugin "discord".'], diff --git a/src/cli/update-cli/post-core-plugin-convergence.ts b/src/cli/update-cli/post-core-plugin-convergence.ts index a9e3a9e03de9..7a0ef09d01c4 100644 --- a/src/cli/update-cli/post-core-plugin-convergence.ts +++ b/src/cli/update-cli/post-core-plugin-convergence.ts @@ -151,6 +151,7 @@ function formatPeerLinkPackageReadWarning(failure: { error: unknown }): PostCore export async function runPostCorePluginConvergence(params: { cfg: OpenClawConfig; env: NodeJS.ProcessEnv; + compatibilityHostVersion?: string; /** * Optional in-memory install records from earlier post-core steps (e.g. * `syncPluginsForUpdateChannel`, `updateNpmInstalledPlugins`) whose @@ -165,7 +166,7 @@ export async function runPostCorePluginConvergence(params: { }): Promise { const env: NodeJS.ProcessEnv = { ...params.env, - OPENCLAW_COMPATIBILITY_HOST_VERSION: VERSION, + OPENCLAW_COMPATIBILITY_HOST_VERSION: params.compatibilityHostVersion ?? VERSION, [UPDATE_POST_CORE_CONVERGENCE_ENV]: "1", }; const prunedBaseline = params.baselineInstallRecords diff --git a/src/commands/doctor-config-preflight-plugin-verification.ts b/src/commands/doctor-config-preflight-plugin-verification.ts index 4a846e847c2e..0c5f000e4cec 100644 --- a/src/commands/doctor-config-preflight-plugin-verification.ts +++ b/src/commands/doctor-config-preflight-plugin-verification.ts @@ -8,6 +8,7 @@ import { formatPluginVerificationDiagnostic, type DegradedPlugin, } from "../plugins/runtime-degraded-state.js"; +import { resolveCompatibilityHostVersion } from "../version.js"; import { measureDoctorConfigPreflightStep } from "./doctor-config-preflight-measure.js"; type StartupPluginVerificationDiagnostic = { @@ -95,6 +96,7 @@ export async function runStartupUpgradeConvergence(params: { runPostCorePluginConvergence({ cfg: params.cfg, env: params.env, + compatibilityHostVersion: resolveCompatibilityHostVersion(params.env), baselineInstallRecords: plan.installRecords, }), params.measure, diff --git a/src/commands/doctor-config-preflight.state-migration.test.ts b/src/commands/doctor-config-preflight.state-migration.test.ts index 44b25e2ebf7f..118969de67ea 100644 --- a/src/commands/doctor-config-preflight.state-migration.test.ts +++ b/src/commands/doctor-config-preflight.state-migration.test.ts @@ -639,6 +639,7 @@ describe("runDoctorConfigPreflight state migration", () => { expect(runPostCorePluginConvergence).toHaveBeenCalledWith({ cfg: { gateway: { mode: "local", port: 19091 } }, env: process.env, + compatibilityHostVersion: expect.any(String), baselineInstallRecords: {}, }); expect(recordSuccessfulStartupMigrations).toHaveBeenCalledWith({ @@ -649,6 +650,33 @@ describe("runDoctorConfigPreflight state migration", () => { expect(startupMigrationLeaseRelease).toHaveBeenCalledOnce(); }); + it("pins startup plugin convergence to the explicit compatibility host version", async () => { + needsStartupMigrationCheckpoint.mockReturnValue(true); + const previousHostVersion = process.env.OPENCLAW_COMPATIBILITY_HOST_VERSION; + process.env.OPENCLAW_COMPATIBILITY_HOST_VERSION = "2026.7.2-beta.7"; + + try { + await runDoctorConfigPreflight({ + migrateLegacyConfig: false, + invalidConfigNote: false, + requireStartupMigrationCheckpoint: true, + }); + } finally { + if (previousHostVersion === undefined) { + delete process.env.OPENCLAW_COMPATIBILITY_HOST_VERSION; + } else { + process.env.OPENCLAW_COMPATIBILITY_HOST_VERSION = previousHostVersion; + } + } + + expect(runPostCorePluginConvergence).toHaveBeenCalledWith({ + cfg: { gateway: { mode: "local", port: 19091 } }, + env: expect.any(Object), + compatibilityHostVersion: "2026.7.2-beta.7", + baselineInstallRecords: {}, + }); + }); + it("refuses startup when plugin migration inputs change during convergence", async () => { needsStartupMigrationCheckpoint.mockReturnValue(true); pluginMigrationFingerprint