mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:20:43 +00:00
fix(update): tolerate legacy doctor metadata handoff
This commit is contained in:
@@ -106,7 +106,7 @@ Options:
|
||||
--provider <openai|anthropic|minimax>
|
||||
Provider auth/model lane. Default: openai
|
||||
--model <provider/model> Override the model used for the agent-turn smoke.
|
||||
Default: openai/gpt-5.5 for the OpenAI lane
|
||||
Default: openai/gpt-5.4 for the OpenAI lane
|
||||
--api-key-env <var> Host env var name for provider API key.
|
||||
Default: OPENAI_API_KEY for openai, ANTHROPIC_API_KEY for anthropic
|
||||
--openai-api-key-env <var> Alias for --api-key-env (backward compatible)
|
||||
@@ -209,7 +209,7 @@ case "$PROVIDER" in
|
||||
openai)
|
||||
AUTH_CHOICE="openai-api-key"
|
||||
AUTH_KEY_FLAG="openai-api-key"
|
||||
[[ "$MODEL_ID_EXPLICIT" -eq 1 ]] || MODEL_ID="${OPENCLAW_PARALLELS_OPENAI_MODEL:-openai/gpt-5.5}"
|
||||
[[ "$MODEL_ID_EXPLICIT" -eq 1 ]] || MODEL_ID="${OPENCLAW_PARALLELS_OPENAI_MODEL:-openai/gpt-5.4}"
|
||||
[[ -n "$API_KEY_ENV" ]] || API_KEY_ENV="OPENAI_API_KEY"
|
||||
;;
|
||||
anthropic)
|
||||
|
||||
@@ -144,7 +144,7 @@ Options:
|
||||
--provider <openai|anthropic|minimax>
|
||||
Provider auth/model lane. Default: openai
|
||||
--model <provider/model> Override the model used for the agent-turn smoke.
|
||||
Default: openai/gpt-5.5 for the OpenAI lane
|
||||
Default: openai/gpt-5.4 for the OpenAI lane
|
||||
--api-key-env <var> Host env var name for provider API key.
|
||||
Default: OPENAI_API_KEY for openai, ANTHROPIC_API_KEY for anthropic
|
||||
--openai-api-key-env <var> Alias for --api-key-env (backward compatible)
|
||||
@@ -266,7 +266,7 @@ case "$PROVIDER" in
|
||||
openai)
|
||||
AUTH_CHOICE="openai-api-key"
|
||||
AUTH_KEY_FLAG="openai-api-key"
|
||||
[[ "$MODEL_ID_EXPLICIT" -eq 1 ]] || MODEL_ID="${OPENCLAW_PARALLELS_OPENAI_MODEL:-openai/gpt-5.5}"
|
||||
[[ "$MODEL_ID_EXPLICIT" -eq 1 ]] || MODEL_ID="${OPENCLAW_PARALLELS_OPENAI_MODEL:-openai/gpt-5.4}"
|
||||
[[ -n "$API_KEY_ENV" ]] || API_KEY_ENV="OPENAI_API_KEY"
|
||||
;;
|
||||
anthropic)
|
||||
|
||||
@@ -122,7 +122,7 @@ Options:
|
||||
--provider <openai|anthropic|minimax>
|
||||
Provider auth/model lane. Default: openai
|
||||
--model <provider/model> Override the model used for agent-turn smoke checks.
|
||||
Default: openai/gpt-5.5 for the OpenAI lane
|
||||
Default: openai/gpt-5.4 for the OpenAI lane
|
||||
--api-key-env <var> Host env var name for provider API key.
|
||||
Default: OPENAI_API_KEY for openai, ANTHROPIC_API_KEY for anthropic
|
||||
--openai-api-key-env <var> Alias for --api-key-env (backward compatible)
|
||||
@@ -214,7 +214,7 @@ case "$PROVIDER" in
|
||||
openai)
|
||||
AUTH_CHOICE="openai-api-key"
|
||||
AUTH_KEY_FLAG="openai-api-key"
|
||||
[[ "$MODEL_ID_EXPLICIT" -eq 1 ]] || MODEL_ID="${OPENCLAW_PARALLELS_OPENAI_MODEL:-openai/gpt-5.5}"
|
||||
[[ "$MODEL_ID_EXPLICIT" -eq 1 ]] || MODEL_ID="${OPENCLAW_PARALLELS_OPENAI_MODEL:-openai/gpt-5.4}"
|
||||
[[ -n "$API_KEY_ENV" ]] || API_KEY_ENV="OPENAI_API_KEY"
|
||||
;;
|
||||
anthropic)
|
||||
|
||||
@@ -140,7 +140,7 @@ Options:
|
||||
--provider <openai|anthropic|minimax>
|
||||
Provider auth/model lane. Default: openai
|
||||
--model <provider/model> Override the model used for the agent-turn smoke.
|
||||
Default: openai/gpt-5.5 for the OpenAI lane
|
||||
Default: openai/gpt-5.4 for the OpenAI lane
|
||||
--api-key-env <var> Host env var name for provider API key.
|
||||
Default: OPENAI_API_KEY for openai, ANTHROPIC_API_KEY for anthropic
|
||||
--openai-api-key-env <var> Alias for --api-key-env (backward compatible)
|
||||
@@ -257,7 +257,7 @@ case "$PROVIDER" in
|
||||
openai)
|
||||
AUTH_CHOICE="openai-api-key"
|
||||
AUTH_KEY_FLAG="openai-api-key"
|
||||
[[ "$MODEL_ID_EXPLICIT" -eq 1 ]] || MODEL_ID="${OPENCLAW_PARALLELS_OPENAI_MODEL:-openai/gpt-5.5}"
|
||||
[[ "$MODEL_ID_EXPLICIT" -eq 1 ]] || MODEL_ID="${OPENCLAW_PARALLELS_OPENAI_MODEL:-openai/gpt-5.4}"
|
||||
[[ -n "$API_KEY_ENV" ]] || API_KEY_ENV="OPENAI_API_KEY"
|
||||
;;
|
||||
anthropic)
|
||||
|
||||
@@ -94,6 +94,8 @@ const POST_CORE_UPDATE_ENV = "OPENCLAW_UPDATE_POST_CORE";
|
||||
const POST_CORE_UPDATE_CHANNEL_ENV = "OPENCLAW_UPDATE_POST_CORE_CHANNEL";
|
||||
const POST_CORE_UPDATE_REQUESTED_CHANNEL_ENV = "OPENCLAW_UPDATE_POST_CORE_REQUESTED_CHANNEL";
|
||||
const POST_CORE_UPDATE_RESULT_PATH_ENV = "OPENCLAW_UPDATE_POST_CORE_RESULT_PATH";
|
||||
const UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE_ENV =
|
||||
"OPENCLAW_UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE";
|
||||
const SERVICE_REFRESH_PATH_ENV_KEYS = [
|
||||
"OPENCLAW_HOME",
|
||||
"OPENCLAW_STATE_DIR",
|
||||
@@ -569,6 +571,7 @@ async function runPackageInstallUpdate(params: {
|
||||
env: {
|
||||
...disableUpdatedPackageCompileCacheEnv(process.env),
|
||||
OPENCLAW_UPDATE_IN_PROGRESS: "1",
|
||||
[UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE_ENV]: "1",
|
||||
},
|
||||
timeoutMs: params.timeoutMs,
|
||||
progress: params.progress,
|
||||
@@ -1030,6 +1033,7 @@ async function maybeRestartService(params: {
|
||||
defaultRuntime.log(theme.success("Daemon restarted successfully."));
|
||||
defaultRuntime.log("");
|
||||
process.env.OPENCLAW_UPDATE_IN_PROGRESS = "1";
|
||||
process.env[UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE_ENV] = "1";
|
||||
try {
|
||||
const interactiveDoctor =
|
||||
process.stdin.isTTY && !params.opts.json && params.opts.yes !== true;
|
||||
@@ -1040,6 +1044,7 @@ async function maybeRestartService(params: {
|
||||
defaultRuntime.log(theme.warn(`Doctor failed: ${String(err)}`));
|
||||
} finally {
|
||||
delete process.env.OPENCLAW_UPDATE_IN_PROGRESS;
|
||||
delete process.env[UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE_ENV];
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveDoctorHealthContributions } from "./doctor-health-contributions.js";
|
||||
import {
|
||||
resolveDoctorHealthContributions,
|
||||
shouldSkipLegacyUpdateDoctorMetadataWrite,
|
||||
} from "./doctor-health-contributions.js";
|
||||
|
||||
describe("doctor health contributions", () => {
|
||||
it("repairs bundled runtime deps before channel-owned doctor paths can import runtimes", () => {
|
||||
@@ -20,11 +23,47 @@ describe("doctor health contributions", () => {
|
||||
expect(ids.indexOf("doctor:plugin-registry")).toBeGreaterThan(-1);
|
||||
expect(ids.indexOf("doctor:plugin-registry")).toBeLessThan(ids.indexOf("doctor:write-config"));
|
||||
});
|
||||
|
||||
it("checks command owner configuration before final config writes", () => {
|
||||
const ids = resolveDoctorHealthContributions().map((entry) => entry.id);
|
||||
|
||||
expect(ids.indexOf("doctor:command-owner")).toBeGreaterThan(-1);
|
||||
expect(ids.indexOf("doctor:command-owner")).toBeLessThan(ids.indexOf("doctor:write-config"));
|
||||
});
|
||||
|
||||
it("skips metadata-only doctor writes under legacy update parents", () => {
|
||||
expect(
|
||||
shouldSkipLegacyUpdateDoctorMetadataWrite({
|
||||
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", () => {
|
||||
expect(
|
||||
shouldSkipLegacyUpdateDoctorMetadataWrite({
|
||||
env: { OPENCLAW_UPDATE_IN_PROGRESS: "1" },
|
||||
before: { gateway: { mode: "local" } },
|
||||
after: { gateway: { mode: "remote" } },
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps current update parents writable", () => {
|
||||
expect(
|
||||
shouldSkipLegacyUpdateDoctorMetadataWrite({
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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";
|
||||
@@ -39,6 +40,39 @@ export function resolveDoctorMode(cfg: OpenClawConfig): DoctorFlowMode {
|
||||
return cfg.gateway?.mode === "remote" ? "remote" : "local";
|
||||
}
|
||||
|
||||
const UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE_ENV =
|
||||
"OPENCLAW_UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE";
|
||||
|
||||
function isTruthyEnvValue(value: string | undefined): boolean {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
const normalized = value.trim().toLowerCase();
|
||||
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: {
|
||||
env: NodeJS.ProcessEnv;
|
||||
before: OpenClawConfig;
|
||||
after: OpenClawConfig;
|
||||
}): boolean {
|
||||
if (!isTruthyEnvValue(params.env.OPENCLAW_UPDATE_IN_PROGRESS)) {
|
||||
return false;
|
||||
}
|
||||
if (isTruthyEnvValue(params.env[UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE_ENV])) {
|
||||
return false;
|
||||
}
|
||||
return isDeepStrictEqual(
|
||||
omitDoctorWriteMetadata(params.before),
|
||||
omitDoctorWriteMetadata(params.after),
|
||||
);
|
||||
}
|
||||
|
||||
function createDoctorHealthContribution(params: {
|
||||
id: string;
|
||||
label: string;
|
||||
@@ -495,6 +529,16 @@ async function runWriteConfigHealth(ctx: DoctorHealthFlowContext): Promise<void>
|
||||
command: "doctor",
|
||||
mode: resolveDoctorMode(ctx.cfg),
|
||||
});
|
||||
if (
|
||||
shouldSkipLegacyUpdateDoctorMetadataWrite({
|
||||
env: ctx.env,
|
||||
before: ctx.cfgForPersistence,
|
||||
after: ctx.cfg,
|
||||
})
|
||||
) {
|
||||
ctx.runtime.log("Skipping doctor metadata-only config write during legacy update handoff.");
|
||||
return;
|
||||
}
|
||||
await replaceConfigFile({
|
||||
nextConfig: ctx.cfg,
|
||||
afterWrite: { mode: "auto" },
|
||||
|
||||
@@ -161,6 +161,8 @@ const MAX_LOG_CHARS = 8000;
|
||||
const PREFLIGHT_MAX_COMMITS = 10;
|
||||
const DEFAULT_PACKAGE_NAME = "openclaw";
|
||||
const CORE_PACKAGE_NAMES = new Set([DEFAULT_PACKAGE_NAME]);
|
||||
const UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE_ENV =
|
||||
"OPENCLAW_UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE";
|
||||
const PREFLIGHT_TEMP_PREFIX =
|
||||
process.platform === "win32" ? "ocu-pf-" : "openclaw-update-preflight-";
|
||||
const PREFLIGHT_WORKTREE_DIRNAME = process.platform === "win32" ? "wt" : "worktree";
|
||||
@@ -1292,7 +1294,10 @@ export async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise<
|
||||
const doctorNodePath = await resolveStableNodePath(process.execPath);
|
||||
const doctorArgv = [doctorNodePath, doctorEntry, "doctor", "--non-interactive", "--fix"];
|
||||
const doctorStep = await runStep(
|
||||
step("openclaw doctor", doctorArgv, gitRoot, { OPENCLAW_UPDATE_IN_PROGRESS: "1" }),
|
||||
step("openclaw doctor", doctorArgv, gitRoot, {
|
||||
OPENCLAW_UPDATE_IN_PROGRESS: "1",
|
||||
[UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE_ENV]: "1",
|
||||
}),
|
||||
);
|
||||
steps.push(doctorStep);
|
||||
if (doctorStep.exitCode !== 0) {
|
||||
|
||||
Reference in New Issue
Block a user