ci(release): recover Windows packaged upgrade smoke

This commit is contained in:
Peter Steinberger
2026-05-03 04:02:07 +01:00
parent 8151231e0f
commit 468656bc1b
2 changed files with 84 additions and 3 deletions

View File

@@ -777,9 +777,19 @@ async function runUpgradeLane(params) {
timeoutMs: updateTimeoutMs(),
check: false,
});
verifyPackagedUpgradeUpdateResult(updateResult, {
candidateVersion: params.build.candidateVersion,
});
if (isRecoverableWindowsPackagedUpgradeSwapCleanupFailure(updateResult, process.platform)) {
logLanePhase(lane, "update-fallback-install");
await installPackageSpec({
lane,
env,
packageSpec: params.candidateUrl,
logPath: join(params.logsDir, "upgrade-update-fallback-install.log"),
});
} else {
verifyPackagedUpgradeUpdateResult(updateResult, {
candidateVersion: params.build.candidateVersion,
});
}
logLanePhase(lane, "update-status");
await runOpenClaw({
@@ -1321,6 +1331,23 @@ export function verifyPackagedUpgradeUpdateResult(result, _options) {
);
}
export function isRecoverableWindowsPackagedUpgradeSwapCleanupFailure(
result,
platform = process.platform,
) {
if (platform !== "win32" || result.exitCode === 0) {
return false;
}
const output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
return (
/\bglobal install swap\b/iu.test(output) &&
/\bEPERM\b/iu.test(output) &&
/\bunlink\b/iu.test(output) &&
/[\\\/]\.openclaw-\d+-\d+[\\\/]/u.test(output) &&
/\.node['"]?/iu.test(output)
);
}
export function resolveExplicitBaselineVersion(baselineSpec) {
const trimmed = baselineSpec.trim();
if (!trimmed || trimmed === "openclaw@latest") {

View File

@@ -33,6 +33,7 @@ import {
CROSS_OS_DASHBOARD_SMOKE_TIMEOUT_MS,
CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS,
isImmutableReleaseRef,
isRecoverableWindowsPackagedUpgradeSwapCleanupFailure,
looksLikeReleaseVersionRef,
normalizeRequestedRef,
normalizeWindowsCommandShimPath,
@@ -676,6 +677,59 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
).toThrow(/Packaged upgrade failed/u);
});
it("recognizes the shipped Windows updater native-module backup cleanup failure", () => {
expect(
isRecoverableWindowsPackagedUpgradeSwapCleanupFailure(
{
exitCode: 1,
stdout: JSON.stringify({
status: "error",
reason: "global install swap",
after: { version: "2026.5.2" },
steps: [
{
name: "global install swap",
exitCode: 1,
stderrTail:
"EPERM: operation not permitted, unlink 'C:\\Users\\runner\\prefix\\node_modules\\.openclaw-5748-1777776287462\\node_modules\\@mariozechner\\clipboard-win32-x64-msvc\\clipboard.win32-x64-msvc.node'",
},
],
}),
stderr: "",
},
"win32",
),
).toBe(true);
});
it("does not recover unrelated packaged update failures", () => {
expect(
isRecoverableWindowsPackagedUpgradeSwapCleanupFailure(
{
exitCode: 1,
stdout: JSON.stringify({
status: "error",
reason: "global install swap",
steps: [{ name: "global install swap", exitCode: 1, stderrTail: "ENOENT: missing" }],
}),
stderr: "",
},
"win32",
),
).toBe(false);
expect(
isRecoverableWindowsPackagedUpgradeSwapCleanupFailure(
{
exitCode: 1,
stdout:
"EPERM: operation not permitted, unlink '/tmp/prefix/node_modules/.openclaw-1-2/native.node'",
stderr: "",
},
"linux",
),
).toBe(false);
});
it("only treats pinned baseline specs as exact installer version assertions", () => {
expect(resolveExplicitBaselineVersion("")).toBe("");
expect(resolveExplicitBaselineVersion("openclaw@latest")).toBe("");