fix(cli): suppress self-update version warnings

This commit is contained in:
Vincent Koc
2026-05-25 08:07:55 +02:00
parent aa50c51902
commit e2bd20f0aa
4 changed files with 122 additions and 1 deletions
+1
View File
@@ -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.
+86 -1
View File
@@ -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
+16
View File
@@ -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(
{
+19
View File
@@ -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<T>(run: () => Promise<T>): Promise<T> {
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<void> {
return await withUpdateInProgressEnv(async () => {
await updateCommandInternal(opts);
});
}
async function updateCommandInternal(opts: UpdateCommandOptions): Promise<void> {
suppressDeprecations();
await cleanupStaleManagedServiceUpdateHandoffs().catch(() => undefined);
const invocationCwd = tryResolveInvocationCwd();