From e2bd20f0aa4330e1788bab85434d3e8e4b42e6ec Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 25 May 2026 08:07:55 +0200 Subject: [PATCH] fix(cli): suppress self-update version warnings --- CHANGELOG.md | 1 + scripts/docker/install-sh-smoke/run.sh | 87 +++++++++++++++++++++++++- src/cli/update-cli.test.ts | 16 +++++ src/cli/update-cli/update-command.ts | 19 ++++++ 4 files changed, 122 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a296319a5b7..c4ab8946bc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai - WebChat: keep message-tool replies visible in the chat while still summarizing internal tool results for the model. Fixes #86347. Thanks @shakkernerd. - Agents/commitments: serialize commitment store load-modify-save writes so concurrent heartbeat and CLI updates no longer lose dismissal, sent, or attempt state. (#81153) Thanks @ai-hpc. +- CLI: suppress benign self-update version-skew warnings during package post-update finalization. - Gateway/perf: tighten restart and startup benchmark failure handling so long profiling runs, failed probes, and fresh Linux runners no longer produce false passing or `n/a` results. - Checks: keep intentional Knip unused-file findings optional so full CI and sparse proof workspaces stay aligned. - Docker: restore writable `~/.config` in runtime images. Fixes #85968. Thanks @hkoessler and @Bartok9. diff --git a/scripts/docker/install-sh-smoke/run.sh b/scripts/docker/install-sh-smoke/run.sh index 00b5e4a81bf..ab8f4a2fbe0 100755 --- a/scripts/docker/install-sh-smoke/run.sh +++ b/scripts/docker/install-sh-smoke/run.sh @@ -13,6 +13,7 @@ UPDATE_BASELINE_VERSION="${OPENCLAW_INSTALL_UPDATE_BASELINE:-latest}" UPDATE_BASELINE_TAG_URL="${OPENCLAW_INSTALL_UPDATE_BASELINE_TAG_URL:-}" UPDATE_EXPECT_VERSION="${OPENCLAW_INSTALL_UPDATE_EXPECT_VERSION:-}" UPDATE_TAG_URL="${OPENCLAW_INSTALL_UPDATE_TAG_URL:-}" +SELF_UPDATE_WARNING_FIXED_VERSION="${OPENCLAW_INSTALL_SELF_UPDATE_WARNING_FIXED_VERSION:-2026.5.25}" FRESHNESS_VERSION="${OPENCLAW_INSTALL_FRESHNESS_VERSION:-latest}" # npm min-release-age is days; 10000 keeps the control failure independent of normal release cadence. FRESHNESS_MIN_RELEASE_AGE="${OPENCLAW_INSTALL_FRESHNESS_MIN_RELEASE_AGE:-10000}" @@ -120,6 +121,75 @@ is_self_swapped_package_process_exit() { [[ "$stderr" == *"/node_modules/openclaw/dist/"* ]] } +is_version_before() { + local candidate="$1" + local floor="$2" + node - "$candidate" "$floor" <<'NODE' +const [, , candidate, floor] = process.argv; +function parse(version) { + const [core, prerelease = ""] = String(version).split("-", 2); + return { + core: core.split(".").map((part) => Number.parseInt(part, 10) || 0), + prerelease: prerelease ? prerelease.split(".") : [], + }; +} +function comparePrerelease(left, right) { + if (left.length === 0 && right.length === 0) { + return 0; + } + if (left.length === 0) { + return 1; + } + if (right.length === 0) { + return -1; + } + for (let index = 0; index < Math.max(left.length, right.length); index += 1) { + const l = left[index]; + const r = right[index]; + if (l === undefined) { + return -1; + } + if (r === undefined) { + return 1; + } + const ln = Number.parseInt(l, 10); + const rn = Number.parseInt(r, 10); + const lNumeric = String(ln) === l; + const rNumeric = String(rn) === r; + if (lNumeric && rNumeric && ln !== rn) { + return ln < rn ? -1 : 1; + } + if (lNumeric !== rNumeric) { + return lNumeric ? -1 : 1; + } + if (l !== r) { + return l < r ? -1 : 1; + } + } + return 0; +} +const left = parse(candidate); +const right = parse(floor); +for (let index = 0; index < Math.max(left.core.length, right.core.length); index += 1) { + const l = left.core[index] ?? 0; + const r = right.core[index] ?? 0; + if (l < r) { + process.exit(0); + } + if (l > r) { + process.exit(1); + } +} +const prereleaseOrder = comparePrerelease(left.prerelease, right.prerelease); +process.exit(prereleaseOrder < 0 ? 0 : 1); +NODE +} + +allow_legacy_update_warning() { + [[ "${OPENCLAW_INSTALL_ALLOW_LEGACY_UPDATE_WARNING:-0}" == "1" ]] && return 0 + is_version_before "$UPDATE_BASELINE_VERSION" "$SELF_UPDATE_WARNING_FIXED_VERSION" +} + npm_install_global() { local label="$1" shift @@ -268,11 +338,20 @@ run_update_smoke() { local update_status local update_stderr_file local update_stderr + local update_env=( + env + npm_config_omit=optional + NPM_CONFIG_OMIT=optional + OPENCLAW_ALLOW_ROOT=1 + ) + if allow_legacy_update_warning; then + update_env+=(OPENCLAW_UPDATE_IN_PROGRESS=1) + fi update_stderr_file="$(mktemp)" set +e UPDATE_JSON="$( run_with_heartbeat "openclaw update" \ - env npm_config_omit=optional NPM_CONFIG_OMIT=optional OPENCLAW_ALLOW_ROOT=1 \ + "${update_env[@]}" \ openclaw update --tag "$UPDATE_TAG_URL" --yes --json 2>"$update_stderr_file" )" update_status=$? @@ -283,6 +362,12 @@ run_update_smoke() { if [[ -n "$update_stderr" ]]; then printf "%s\n" "$update_stderr" >&2 fi + if [[ "$update_stderr" == *"config was written by version"* ]] && allow_legacy_update_warning; then + echo "WARN: legacy baseline emitted a self-update version-skew warning; fixed baselines must not" >&2 + elif [[ "$update_stderr" == *"config was written by version"* ]]; then + echo "ERROR: openclaw update emitted a self-update version-skew warning" >&2 + return 1 + fi if [[ "$update_status" -ne 0 ]]; then if is_self_swapped_package_process_exit "$update_stderr"; then echo "WARN: legacy updater process exited after self-swap; validating update JSON and installed CLI" >&2 diff --git a/src/cli/update-cli.test.ts b/src/cli/update-cli.test.ts index 6c00c3e6cda..7c15a05bba5 100644 --- a/src/cli/update-cli.test.ts +++ b/src/cli/update-cli.test.ts @@ -893,6 +893,7 @@ describe("update-cli", () => { expect(call?.[1]).toEqual([entrypoints[0], "update", "--yes", "--timeout", "1800"]); expect(call?.[2]?.stdio).toBe("inherit"); expect(call?.[2]?.env?.NODE_DISABLE_COMPILE_CACHE).toBe("1"); + expect(call?.[2]?.env?.OPENCLAW_UPDATE_IN_PROGRESS).toBe("1"); 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"); @@ -5230,6 +5231,21 @@ describe("update-cli", () => { } }); + it("marks the whole update command as update-in-progress", async () => { + await withEnvAsync({ OPENCLAW_UPDATE_IN_PROGRESS: undefined }, async () => { + let observedUpdateEnv: string | undefined; + vi.mocked(runGatewayUpdate).mockImplementationOnce(async () => { + observedUpdateEnv = process.env.OPENCLAW_UPDATE_IN_PROGRESS; + return makeOkUpdateResult(); + }); + + await updateCommand({ restart: false }); + + expect(observedUpdateEnv).toBe("1"); + expect(process.env.OPENCLAW_UPDATE_IN_PROGRESS).toBeUndefined(); + }); + }); + it("updateFinalizeCommand runs doctor and plugin convergence with full update env", async () => { await withEnvAsync( { diff --git a/src/cli/update-cli/update-command.ts b/src/cli/update-cli/update-command.ts index 9176cd639b4..4b7e952382c 100644 --- a/src/cli/update-cli/update-command.ts +++ b/src/cli/update-cli/update-command.ts @@ -2750,6 +2750,7 @@ async function continuePostCoreUpdateInFreshProcess(params: { stdio: childStdio, env: { ...stripGatewayServiceMarkerEnv(disableUpdatedPackageCompileCacheEnv(process.env)), + OPENCLAW_UPDATE_IN_PROGRESS: "1", [POST_CORE_UPDATE_ENV]: "1", [POST_CORE_UPDATE_CHANNEL_ENV]: params.channel, ...(params.requestedChannel @@ -2906,7 +2907,25 @@ async function markControlPlaneUpdateRestartSentinelFailureBestEffort(params: { } } +async function withUpdateInProgressEnv(run: () => Promise): Promise { + const previousUpdateInProgress = process.env.OPENCLAW_UPDATE_IN_PROGRESS; + process.env.OPENCLAW_UPDATE_IN_PROGRESS = "1"; + return run().finally(() => { + if (previousUpdateInProgress === undefined) { + delete process.env.OPENCLAW_UPDATE_IN_PROGRESS; + } else { + process.env.OPENCLAW_UPDATE_IN_PROGRESS = previousUpdateInProgress; + } + }); +} + export async function updateCommand(opts: UpdateCommandOptions): Promise { + return await withUpdateInProgressEnv(async () => { + await updateCommandInternal(opts); + }); +} + +async function updateCommandInternal(opts: UpdateCommandOptions): Promise { suppressDeprecations(); await cleanupStaleManagedServiceUpdateHandoffs().catch(() => undefined); const invocationCwd = tryResolveInvocationCwd();