fix(cli): block gateway-owned package updates (#75819)

Merged via squash.

Prepared head SHA: acdf73e6d0
Co-authored-by: ai-hpc <183861985+ai-hpc@users.noreply.github.com>
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Reviewed-by: @hxy91819
This commit is contained in:
NVIDIAN
2026-05-02 20:40:36 -07:00
committed by GitHub
parent 55c738ad4b
commit c81c0171cd
4 changed files with 69 additions and 1 deletions

View File

@@ -29,6 +29,7 @@ const runRestartScript = vi.fn();
const mockedRunDaemonInstall = vi.fn();
const serviceReadCommand = vi.fn();
const serviceReadRuntime = vi.fn();
const mockGetSelfAndAncestorPidsSync = vi.fn(() => new Set<number>([process.pid]));
const inspectPortUsage = vi.fn();
const classifyPortListener = vi.fn();
const formatPortDiagnostics = vi.fn();
@@ -128,6 +129,10 @@ vi.mock("../infra/runtime-guard.js", () => ({
},
}));
vi.mock("../infra/restart-stale-pids.js", () => ({
getSelfAndAncestorPidsSync: () => mockGetSelfAndAncestorPidsSync(),
}));
vi.mock("node:child_process", async () => {
const actual = await vi.importActual<typeof import("node:child_process")>("node:child_process");
return {
@@ -498,6 +503,7 @@ describe("update-cli", () => {
pid: 4242,
state: "running",
});
mockGetSelfAndAncestorPidsSync.mockReturnValue(new Set<number>([process.pid]));
prepareRestartScript.mockResolvedValue("/tmp/openclaw-restart-test.sh");
runRestartScript.mockResolvedValue(undefined);
inspectPortUsage.mockResolvedValue({
@@ -1425,6 +1431,26 @@ describe("update-cli", () => {
);
});
it("refuses package updates from inside the active gateway process tree", async () => {
mockPackageInstallStatus(createCaseDir("openclaw-update"));
serviceLoaded.mockResolvedValue(true);
mockGetSelfAndAncestorPidsSync.mockReturnValue(new Set<number>([process.pid, 4242]));
await updateCommand({ yes: true });
const errors = vi.mocked(defaultRuntime.error).mock.calls.map((call) => String(call[0]));
expect(errors.join("\n")).toContain(
"openclaw update detected it is running inside the gateway process tree.",
);
expect(errors.join("\n")).toContain("Gateway PID 4242 is an ancestor");
expect(defaultRuntime.exit).toHaveBeenCalledWith(1);
expect(serviceStop).not.toHaveBeenCalled();
expect(runCommandWithTimeout).not.toHaveBeenCalledWith(
["npm", "i", "-g", "openclaw@latest", "--no-fund", "--no-audit", "--loglevel=error"],
expect.any(Object),
);
});
it("blocks package updates when the target requires a newer Node runtime", async () => {
mockPackageInstallStatus(createCaseDir("openclaw-update"));
vi.mocked(fetchNpmPackageTargetStatus).mockResolvedValue({