From 6cc824433374bf6423a81063f5d921a0d4aefcfa Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 24 May 2026 21:42:39 +0200 Subject: [PATCH] fix(update): suppress internal handoff version warnings --- CHANGELOG.md | 1 + src/cli/update-cli.test.ts | 10 ++++++++- src/cli/update-cli/update-command.ts | 10 +++++++-- src/config/io.compat.test.ts | 32 ++++++++++++++++++++++++++++ src/config/io.ts | 19 +++++++++++++---- 5 files changed, 65 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe63c3b5834..86e4d208552 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Gateway/update: avoid fetching unrelated tags during dev-channel git updates so moved release tags do not block branch-based updates. (#84737) Thanks @rubencu. +- CLI/update: suppress the expected future-config warning while an old update parent hands off to the freshly installed post-core process. - MiniMax: store OAuth token expiry as an absolute millisecond timestamp so OAuth profiles no longer appear expired on every request. (#83480) Thanks @NianJiuZst. - Agents/Anthropic: strip missing or blank thinking signatures for signed-thinking providers even when recovery supplies a narrow replay policy without signature preservation. Fixes #84430. (#84448) Thanks @NianJiuZst. - Agents/channels: send a visible notice when an aborted main session cannot be resumed after restart, including Telegram group targets. (#85805) Thanks @pfrederiksen. diff --git a/src/cli/update-cli.test.ts b/src/cli/update-cli.test.ts index 4fc55affdcf..6c00c3e6cda 100644 --- a/src/cli/update-cli.test.ts +++ b/src/cli/update-cli.test.ts @@ -896,6 +896,10 @@ describe("update-cli", () => { expect(call?.[2]?.env?.OPENCLAW_UPDATE_POST_CORE).toBe("1"); expect(call?.[2]?.env?.OPENCLAW_UPDATE_POST_CORE_CHANNEL).toBe("dev"); expect(call?.[2]?.env?.OPENCLAW_COMPATIBILITY_HOST_VERSION).toBe("1.0.0"); + expect(vi.mocked(readConfigFileSnapshot).mock.calls[1]?.[0]).toEqual({ + skipPluginValidation: true, + suppressFutureVersionWarning: true, + }); expect(updateNpmInstalledPlugins).not.toHaveBeenCalled(); expect(runDaemonInstall).not.toHaveBeenCalled(); expect(runDaemonRestart).not.toHaveBeenCalled(); @@ -1179,7 +1183,11 @@ describe("update-cli", () => { expect( vi .mocked(readConfigFileSnapshot) - .mock.calls.some(([options]) => options?.skipPluginValidation === true), + .mock.calls.some( + ([options]) => + options?.skipPluginValidation === true && + options.suppressFutureVersionWarning === true, + ), ).toBe(true); expect(defaultRuntime.exit).toHaveBeenCalledWith(0); expect(syncPluginsForUpdateChannel).toHaveBeenCalledTimes(1); diff --git a/src/cli/update-cli/update-command.ts b/src/cli/update-cli/update-command.ts index 22a74ef0e2a..9176cd639b4 100644 --- a/src/cli/update-cli/update-command.ts +++ b/src/cli/update-cli/update-command.ts @@ -2950,7 +2950,10 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise { process.env.OPENCLAW_COMPATIBILITY_HOST_VERSION = (await readPackageVersion(root)) ?? VERSION; - let postCoreConfigSnapshot = await readConfigFileSnapshot({ skipPluginValidation: true }); + let postCoreConfigSnapshot = await readConfigFileSnapshot({ + skipPluginValidation: true, + suppressFutureVersionWarning: true, + }); const preUpdateSourceConfig = await readPostCorePreUpdateSourceConfig({ sourceConfigPath: process.env[POST_CORE_UPDATE_SOURCE_CONFIG_PATH_ENV], currentSnapshot: postCoreConfigSnapshot, @@ -3416,7 +3419,10 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise { let postUpdateConfigSnapshot = result.status === "ok" && !opts.dryRun - ? await readConfigFileSnapshot({ skipPluginValidation: true }) + ? await readConfigFileSnapshot({ + skipPluginValidation: true, + suppressFutureVersionWarning: shouldResumePostCoreInFreshProcess, + }) : configSnapshot; if (!shouldResumePostCoreInFreshProcess) { postUpdateConfigSnapshot = await persistRequestedUpdateChannel({ diff --git a/src/config/io.compat.test.ts b/src/config/io.compat.test.ts index 2bb11c2a048..adafeac4485 100644 --- a/src/config/io.compat.test.ts +++ b/src/config/io.compat.test.ts @@ -150,6 +150,38 @@ describe("config io paths", () => { }); }); + it("does not warn about newer config during internal update handoff reads", async () => { + await withTempHome(async (home) => { + const configPath = path.join(home, ".openclaw", "openclaw.json"); + await fs.mkdir(path.dirname(configPath), { recursive: true }); + await fs.writeFile( + configPath, + JSON.stringify( + { + meta: { lastTouchedVersion: "9999.1.1" }, + gateway: { mode: "local" }, + }, + null, + 2, + ), + ); + const logger = { + error: vi.fn(), + warn: vi.fn(), + }; + + const io = createConfigIO({ + configPath, + env: { OPENCLAW_UPDATE_POST_CORE: "1" } as NodeJS.ProcessEnv, + homedir: () => home, + logger, + }); + io.loadConfig(); + + expect(logger.warn).not.toHaveBeenCalled(); + }); + }); + it("normalizes safe-bin config entries at config load time", () => { const cfg = { tools: { diff --git a/src/config/io.ts b/src/config/io.ts index 70bc849c597..6b797ba3ba2 100644 --- a/src/config/io.ts +++ b/src/config/io.ts @@ -7,6 +7,7 @@ import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent import { ensureOwnerDisplaySecret } from "../agents/owner-display.js"; import { isVerbose } from "../global-state.js"; import { loadDotEnv } from "../infra/dotenv.js"; +import { isTruthyEnvValue } from "../infra/env.js"; import { formatErrorMessage } from "../infra/errors.js"; import { resolveRequiredHomeDir } from "../infra/home-dir.js"; import { replaceFileAtomic, replaceFileAtomicSync } from "../infra/replace-file.js"; @@ -956,6 +957,7 @@ export type ConfigSnapshotReadOptions = { observe?: boolean; skipPluginValidation?: boolean; preservedLegacyRootKeys?: readonly string[]; + suppressFutureVersionWarning?: boolean; }; function warnOnConfigMiskeys(raw: unknown, logger: Pick): void { @@ -997,6 +999,13 @@ function warnIfConfigFromFuture(cfg: OpenClawConfig, logger: Pick): string { if (deps.configPath) { return deps.configPath; @@ -1005,16 +1014,17 @@ function resolveConfigPathForDeps(deps: Required): string { } function normalizeDeps(overrides: ConfigIoDeps = {}): Required { + const env = overrides.env ?? process.env; return { fs: overrides.fs ?? fs, json5: overrides.json5 ?? JSON5, - env: overrides.env ?? process.env, - homedir: - overrides.homedir ?? (() => resolveRequiredHomeDir(overrides.env ?? process.env, os.homedir)), + env, + homedir: overrides.homedir ?? (() => resolveRequiredHomeDir(env, os.homedir)), configPath: overrides.configPath ?? "", logger: overrides.logger ?? console, measure: overrides.measure ?? (async (_name, run) => await run()), - suppressFutureVersionWarning: overrides.suppressFutureVersionWarning ?? false, + suppressFutureVersionWarning: + overrides.suppressFutureVersionWarning ?? shouldSuppressFutureVersionWarningForEnv(env), observe: overrides.observe ?? true, }; } @@ -2544,6 +2554,7 @@ export async function readConfigFileSnapshot( ...(options.measure ? { measure: options.measure } : {}), ...(options.observe === false ? { observe: false } : {}), ...(options.skipPluginValidation ? { pluginValidation: "skip" } : {}), + ...(options.suppressFutureVersionWarning ? { suppressFutureVersionWarning: true } : {}), ...(options.preservedLegacyRootKeys ? { preservedLegacyRootKeys: options.preservedLegacyRootKeys } : {}),