fix(update): fallback to --omit=optional when global npm update fails (#24896)

* fix(update): fallback to --omit=optional when global npm update fails

* fix(update): add recovery hints and fallback for npm global update failures

* chore(update): align fallback progress step index ordering

* chore(update): label omit-optional retry step in progress output

* chore(update): avoid showing 1/2 when fallback path is not used

* chore(ci): retrigger after unrelated test OOM

* fix(update): scope recovery hints to npm failures

* test(update): cover non-npm hint suppression

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
Xinhua Gu
2026-02-27 03:35:13 +01:00
committed by GitHub
parent 418111adb9
commit 7bbfb9de5e
5 changed files with 184 additions and 3 deletions

View File

@@ -0,0 +1,56 @@
import { describe, expect, it } from "vitest";
import type { UpdateRunResult } from "../../infra/update-runner.js";
import { inferUpdateFailureHints } from "./progress.js";
function makeResult(
stepName: string,
stderrTail: string,
mode: UpdateRunResult["mode"] = "npm",
): UpdateRunResult {
return {
status: "error",
mode,
reason: stepName,
steps: [
{
name: stepName,
command: "npm i -g openclaw@latest",
cwd: "/tmp",
durationMs: 1,
exitCode: 1,
stderrTail,
},
],
durationMs: 1,
};
}
describe("inferUpdateFailureHints", () => {
it("returns EACCES hint for global update permission failures", () => {
const result = makeResult(
"global update",
"npm ERR! code EACCES\nnpm ERR! Error: EACCES: permission denied",
);
const hints = inferUpdateFailureHints(result);
expect(hints.join("\n")).toContain("EACCES");
expect(hints.join("\n")).toContain("npm config set prefix ~/.local");
});
it("returns native optional dependency hint for node-gyp/opus failures", () => {
const result = makeResult(
"global update",
"node-pre-gyp ERR!\n@discordjs/opus\nnode-gyp rebuild failed",
);
const hints = inferUpdateFailureHints(result);
expect(hints.join("\n")).toContain("--omit=optional");
});
it("does not return npm hints for non-npm install modes", () => {
const result = makeResult(
"global update",
"npm ERR! code EACCES\nnpm ERR! Error: EACCES: permission denied",
"pnpm",
);
expect(inferUpdateFailureHints(result)).toEqual([]);
});
});