fix(update): skip legacy parent doctor config writes

This commit is contained in:
Peter Steinberger
2026-04-29 05:36:51 +01:00
parent b85edb3f0c
commit 3aadeba93f
2 changed files with 21 additions and 58 deletions

View File

@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import {
resolveDoctorHealthContributions,
shouldSkipLegacyUpdateDoctorMetadataWrite,
shouldSkipLegacyUpdateDoctorConfigWrite,
} from "./doctor-health-contributions.js";
describe("doctor health contributions", () => {
@@ -30,54 +30,39 @@ describe("doctor health contributions", () => {
expect(ids.indexOf("doctor:command-owner")).toBeLessThan(ids.indexOf("doctor:write-config"));
});
it("skips metadata-only doctor writes under legacy update parents", () => {
it("skips doctor config writes under legacy update parents", () => {
expect(
shouldSkipLegacyUpdateDoctorMetadataWrite({
shouldSkipLegacyUpdateDoctorConfigWrite({
env: { OPENCLAW_UPDATE_IN_PROGRESS: "1" },
before: { gateway: { mode: "local" }, meta: { lastTouchedVersion: "2026.4.26" } },
after: {
gateway: { mode: "local" },
meta: { lastTouchedVersion: "2026.4.27" },
wizard: { lastRunCommand: "doctor" },
},
}),
).toBe(true);
});
it("keeps real doctor repairs writable during update", () => {
it("keeps doctor writes outside legacy update writable", () => {
expect(
shouldSkipLegacyUpdateDoctorMetadataWrite({
env: { OPENCLAW_UPDATE_IN_PROGRESS: "1" },
before: { gateway: { mode: "local" } },
after: { gateway: { mode: "remote" } },
}),
).toBe(false);
});
it("keeps repair writes from doctor config preflight writable during legacy update", () => {
expect(
shouldSkipLegacyUpdateDoctorMetadataWrite({
env: { OPENCLAW_UPDATE_IN_PROGRESS: "1" },
hasPendingConfigWrite: true,
before: { gateway: { mode: "remote" } },
after: {
gateway: { mode: "remote" },
meta: { lastTouchedVersion: "2026.4.27" },
wizard: { lastRunCommand: "doctor" },
},
shouldSkipLegacyUpdateDoctorConfigWrite({
env: {},
}),
).toBe(false);
});
it("keeps current update parents writable", () => {
expect(
shouldSkipLegacyUpdateDoctorMetadataWrite({
shouldSkipLegacyUpdateDoctorConfigWrite({
env: {
OPENCLAW_UPDATE_IN_PROGRESS: "1",
OPENCLAW_UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE: "1",
},
before: { meta: { lastTouchedVersion: "2026.4.26" } },
after: { meta: { lastTouchedVersion: "2026.4.27" } },
}),
).toBe(false);
});
it("treats falsey update env values as normal writes", () => {
expect(
shouldSkipLegacyUpdateDoctorConfigWrite({
env: {
OPENCLAW_UPDATE_IN_PROGRESS: "0",
},
}),
).toBe(false);
});

View File

@@ -1,5 +1,4 @@
import fs from "node:fs";
import { isDeepStrictEqual } from "node:util";
import type { probeGatewayMemoryStatus } from "../commands/doctor-gateway-health.js";
import type { DoctorOptions, DoctorPrompter } from "../commands/doctor-prompter.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
@@ -52,16 +51,8 @@ function isTruthyEnvValue(value: string | undefined): boolean {
return normalized !== "" && normalized !== "0" && normalized !== "false" && normalized !== "no";
}
function omitDoctorWriteMetadata(cfg: OpenClawConfig): OpenClawConfig {
const { meta: _meta, wizard: _wizard, ...rest } = cfg;
return rest;
}
export function shouldSkipLegacyUpdateDoctorMetadataWrite(params: {
export function shouldSkipLegacyUpdateDoctorConfigWrite(params: {
env: NodeJS.ProcessEnv;
hasPendingConfigWrite?: boolean;
before: OpenClawConfig;
after: OpenClawConfig;
}): boolean {
if (!isTruthyEnvValue(params.env.OPENCLAW_UPDATE_IN_PROGRESS)) {
return false;
@@ -69,13 +60,7 @@ export function shouldSkipLegacyUpdateDoctorMetadataWrite(params: {
if (isTruthyEnvValue(params.env[UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE_ENV])) {
return false;
}
if (params.hasPendingConfigWrite === true) {
return false;
}
return isDeepStrictEqual(
omitDoctorWriteMetadata(params.before),
omitDoctorWriteMetadata(params.after),
);
return true;
}
function createDoctorHealthContribution(params: {
@@ -534,15 +519,8 @@ async function runWriteConfigHealth(ctx: DoctorHealthFlowContext): Promise<void>
command: "doctor",
mode: resolveDoctorMode(ctx.cfg),
});
if (
shouldSkipLegacyUpdateDoctorMetadataWrite({
env: ctx.env ?? process.env,
hasPendingConfigWrite: ctx.configResult.shouldWriteConfig === true,
before: ctx.cfgForPersistence,
after: ctx.cfg,
})
) {
ctx.runtime.log("Skipping doctor metadata-only config write during legacy update handoff.");
if (shouldSkipLegacyUpdateDoctorConfigWrite({ env: ctx.env })) {
ctx.runtime.log("Skipping doctor config write during legacy update handoff.");
return;
}
await replaceConfigFile({