mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
fix(update): continue after package doctor warnings (#91586)
* fix(update): continue after package doctor warnings * fix(update): type advisory step rendering * fix(update): preserve advisory doctor step state * fix(update): share advisory doctor state * fix(update): keep timed-out doctor failures blocking * fix(update): require explicit doctor advisory result * fix(update): reject malformed doctor advisory results * fix(update): bound doctor advisory diagnostics * fix(update): keep doctor advisory restart-neutral * fix(update): protect doctor advisory IPC * fix(update): scope doctor advisories to converging updater * fix(update): scope doctor advisories to deferred repairs * fix(update): secure doctor advisory IPC --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import { theme } from "../../../packages/terminal-core/src/theme.js";
|
||||
import { formatDurationPrecise } from "../../infra/format-time/format-duration.ts";
|
||||
import type {
|
||||
UpdateRunResult,
|
||||
UpdateStepAdvisory,
|
||||
UpdateStepInfo,
|
||||
UpdateStepProgress,
|
||||
} from "../../infra/update-runner.js";
|
||||
@@ -37,10 +38,14 @@ const STEP_LABELS: Record<string, string> = {
|
||||
"global install": "Installing global package",
|
||||
};
|
||||
|
||||
function getStepLabel(step: UpdateStepInfo): string {
|
||||
function getStepLabel(step: Pick<UpdateStepInfo, "name">): string {
|
||||
return STEP_LABELS[step.name] ?? step.name;
|
||||
}
|
||||
|
||||
function isAdvisoryStep(step: { advisory?: UpdateStepAdvisory }): boolean {
|
||||
return step.advisory !== undefined;
|
||||
}
|
||||
|
||||
/** Convert updater failure reasons and stderr tails into operator-facing recovery hints. */
|
||||
export function inferUpdateFailureHints(result: UpdateRunResult): string[] {
|
||||
if (result.status !== "error") {
|
||||
@@ -138,12 +143,19 @@ export function createUpdateProgress(enabled: boolean): ProgressController {
|
||||
|
||||
const label = getStepLabel(step);
|
||||
const duration = theme.muted(`(${formatDurationPrecise(step.durationMs)})`);
|
||||
const icon = step.exitCode === 0 ? theme.success("\u2713") : theme.error("\u2717");
|
||||
const icon = formatStepStatus(step);
|
||||
|
||||
currentSpinner.stop(`${icon} ${label} ${duration}`);
|
||||
currentSpinner = null;
|
||||
|
||||
if (step.exitCode !== 0 && step.stderrTail) {
|
||||
if (isAdvisoryStep(step) && step.stderrTail) {
|
||||
const lines = step.stderrTail.split("\n").slice(-10);
|
||||
for (const line of lines) {
|
||||
if (line.trim()) {
|
||||
defaultRuntime.log(` ${theme.warn(line)}`);
|
||||
}
|
||||
}
|
||||
} else if (step.exitCode !== 0 && step.stderrTail) {
|
||||
const lines = step.stderrTail.split("\n").slice(-10);
|
||||
for (const line of lines) {
|
||||
if (line.trim()) {
|
||||
@@ -165,11 +177,17 @@ export function createUpdateProgress(enabled: boolean): ProgressController {
|
||||
};
|
||||
}
|
||||
|
||||
function formatStepStatus(exitCode: number | null): string {
|
||||
if (exitCode === 0) {
|
||||
function formatStepStatus(step: {
|
||||
exitCode: number | null;
|
||||
advisory?: UpdateStepAdvisory;
|
||||
}): string {
|
||||
if (isAdvisoryStep(step)) {
|
||||
return theme.warn("!");
|
||||
}
|
||||
if (step.exitCode === 0) {
|
||||
return theme.success("\u2713");
|
||||
}
|
||||
if (exitCode === null) {
|
||||
if (step.exitCode === null) {
|
||||
return theme.warn("?");
|
||||
}
|
||||
return theme.error("\u2717");
|
||||
@@ -213,11 +231,18 @@ export function printResult(result: UpdateRunResult, opts: PrintResultOptions):
|
||||
defaultRuntime.log("");
|
||||
defaultRuntime.log(theme.heading("Steps:"));
|
||||
for (const step of result.steps) {
|
||||
const status = formatStepStatus(step.exitCode);
|
||||
const status = formatStepStatus(step);
|
||||
const duration = theme.muted(`(${formatDurationPrecise(step.durationMs)})`);
|
||||
defaultRuntime.log(` ${status} ${step.name} ${duration}`);
|
||||
|
||||
if (step.exitCode !== 0 && step.stderrTail) {
|
||||
if (isAdvisoryStep(step) && step.stderrTail) {
|
||||
const lines = step.stderrTail.split("\n").slice(0, 5);
|
||||
for (const line of lines) {
|
||||
if (line.trim()) {
|
||||
defaultRuntime.log(` ${theme.warn(line)}`);
|
||||
}
|
||||
}
|
||||
} else if (step.exitCode !== 0 && step.stderrTail) {
|
||||
const lines = step.stderrTail.split("\n").slice(0, 5);
|
||||
for (const line of lines) {
|
||||
if (line.trim()) {
|
||||
|
||||
@@ -206,6 +206,9 @@ export async function runUpdateStep(params: {
|
||||
durationMs,
|
||||
exitCode: res.code,
|
||||
stderrTail,
|
||||
signal: res.signal,
|
||||
killed: res.killed,
|
||||
termination: res.termination,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -216,6 +219,9 @@ export async function runUpdateStep(params: {
|
||||
exitCode: res.code,
|
||||
stdoutTail: trimLogTail(res.stdout, MAX_LOG_CHARS),
|
||||
stderrTail,
|
||||
signal: res.signal,
|
||||
killed: res.killed,
|
||||
termination: res.termination,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,10 @@ import {
|
||||
import { createLowDiskSpaceWarning } from "../../infra/disk-space.js";
|
||||
import { pathExists } from "../../infra/fs-safe.js";
|
||||
import { readJsonIfExists, writeJson } from "../../infra/json-files.js";
|
||||
import { runGlobalPackageUpdateSteps } from "../../infra/package-update-steps.js";
|
||||
import {
|
||||
markPackagePostInstallDoctorAdvisory,
|
||||
runGlobalPackageUpdateSteps,
|
||||
} from "../../infra/package-update-steps.js";
|
||||
import { parseStrictPositiveInteger } from "../../infra/parse-finite-number.js";
|
||||
import { getSelfAndAncestorPidsSync } from "../../infra/restart-stale-pids.js";
|
||||
import { nodeVersionSatisfiesEngine } from "../../infra/runtime-guard.js";
|
||||
@@ -75,6 +78,11 @@ import {
|
||||
writeControlPlaneUpdateRestartSentinel,
|
||||
type ControlPlaneUpdateSentinelMetaFile,
|
||||
} from "../../infra/update-control-plane-sentinel.js";
|
||||
import {
|
||||
consumeUpdatePostInstallDoctorResult,
|
||||
createUpdatePostInstallDoctorResultPath,
|
||||
UPDATE_POST_INSTALL_DOCTOR_RESULT_PATH_ENV,
|
||||
} from "../../infra/update-doctor-result.js";
|
||||
import {
|
||||
canResolveRegistryVersionForPackageTarget,
|
||||
createGlobalInstallEnv,
|
||||
@@ -1558,15 +1566,24 @@ async function runPackageInstallUpdate(params: {
|
||||
if (entryPath) {
|
||||
await createUpdateConfigSnapshot();
|
||||
const candidateHostVersion = await readPackageVersion(verifiedPackageRoot);
|
||||
return await runUpdateStep({
|
||||
const doctorResultPath = createUpdatePostInstallDoctorResultPath();
|
||||
const doctorArgv = [
|
||||
params.nodeRunner ?? resolveNodeRunner(),
|
||||
entryPath,
|
||||
"doctor",
|
||||
"--non-interactive",
|
||||
"--fix",
|
||||
];
|
||||
const doctorProgressInfo = {
|
||||
name: `${CLI_NAME} doctor`,
|
||||
argv: [
|
||||
params.nodeRunner ?? resolveNodeRunner(),
|
||||
entryPath,
|
||||
"doctor",
|
||||
"--non-interactive",
|
||||
"--fix",
|
||||
],
|
||||
command: doctorArgv.join(" "),
|
||||
index: 0,
|
||||
total: 0,
|
||||
};
|
||||
params.progress?.onStepStart?.(doctorProgressInfo);
|
||||
const doctorStep = await runUpdateStep({
|
||||
name: `${CLI_NAME} doctor`,
|
||||
argv: doctorArgv,
|
||||
cwd: verifiedPackageRoot,
|
||||
env: {
|
||||
...resolvePostInstallDoctorEnv({
|
||||
@@ -1576,13 +1593,26 @@ async function runPackageInstallUpdate(params: {
|
||||
OPENCLAW_UPDATE_IN_PROGRESS: "1",
|
||||
[UPDATE_DEFER_CONFIGURED_PLUGIN_INSTALL_REPAIR_ENV]: "1",
|
||||
[UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE_ENV]: "1",
|
||||
[UPDATE_POST_INSTALL_DOCTOR_RESULT_PATH_ENV]: doctorResultPath,
|
||||
...(candidateHostVersion === null
|
||||
? {}
|
||||
: { OPENCLAW_COMPATIBILITY_HOST_VERSION: candidateHostVersion }),
|
||||
},
|
||||
timeoutMs: params.timeoutMs,
|
||||
progress: params.progress,
|
||||
});
|
||||
const doctorResult = await consumeUpdatePostInstallDoctorResult(doctorResultPath);
|
||||
const completedDoctorStep = markPackagePostInstallDoctorAdvisory(doctorStep, doctorResult);
|
||||
params.progress?.onStepComplete?.({
|
||||
...doctorProgressInfo,
|
||||
durationMs: completedDoctorStep.durationMs,
|
||||
exitCode: completedDoctorStep.exitCode,
|
||||
stderrTail: completedDoctorStep.stderrTail,
|
||||
signal: completedDoctorStep.signal,
|
||||
killed: completedDoctorStep.killed,
|
||||
termination: completedDoctorStep.termination,
|
||||
advisory: completedDoctorStep.advisory,
|
||||
});
|
||||
return completedDoctorStep;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user