fix: preserve plugin update recovery after commit (#125739)

This commit is contained in:
Peter Steinberger
2026-08-18 02:08:56 -07:00
committed by GitHub
parent 29d6fa6eb2
commit f0dd7eecaa
2 changed files with 54 additions and 47 deletions
+41 -32
View File
@@ -895,6 +895,7 @@ describe("plugins cli update", () => {
expect(notifyGatewayPluginMetadataChangedMock).not.toHaveBeenCalled();
expect(rollback).toHaveBeenCalledTimes(1);
expect(commit).not.toHaveBeenCalled();
expect(pluginsCliRuntimeLogs.join("\n")).not.toContain("Updated");
});
it("rolls back persisted install records when included config changes during a records-only update", async () => {
@@ -1595,24 +1596,23 @@ describe("plugins cli update", () => {
it("keeps durable state when transaction cleanup fails after the write", async () => {
const cfg = {
plugins: {
installs: {
alpha: {
source: "npm",
spec: "@openclaw/alpha@1.0.0",
},
entries: {
alpha: { enabled: true },
},
},
} as OpenClawConfig;
const nextConfig = {
plugins: {
installs: {
alpha: {
source: "npm",
spec: "@openclaw/alpha@1.1.0",
},
},
const previousRecords = {
alpha: {
source: "npm" as const,
spec: "@openclaw/alpha@1.0.0",
},
} as OpenClawConfig;
};
const nextRecords = {
alpha: {
source: "npm" as const,
spec: "@openclaw/alpha@1.1.0",
},
};
const runtimeConfig = {
...cfg,
messages: {
@@ -1620,7 +1620,11 @@ describe("plugins cli update", () => {
},
} as OpenClawConfig;
const nextRuntimeConfig = {
...nextConfig,
...runtimeConfig,
plugins: {
...runtimeConfig.plugins,
installs: nextRecords,
},
messages: runtimeConfig.messages,
} as OpenClawConfig;
primeUpdateConfigSnapshot({
@@ -1630,7 +1634,7 @@ describe("plugins cli update", () => {
"/tmp/plugins.json5": "plugins-start-hash",
},
});
setInstalledPluginIndexInstallRecords(cfg.plugins?.installs ?? {});
setInstalledPluginIndexInstallRecords(previousRecords);
const rollback = vi.fn(async () => undefined);
const failedCommit = vi.fn(async () => {
throw new Error("cleanup failed");
@@ -1651,30 +1655,35 @@ describe("plugins cli update", () => {
config: nextRuntimeConfig,
});
await expect(runPluginsCommand(["plugins", "update", "alpha"])).rejects.toThrow(
"Plugin install transaction commit failed",
);
await runPluginsCommand(["plugins", "update", "alpha"]);
const updateParams = expectSingleCallParams(updateNpmInstalledPluginsMock);
expect(updateParams.config).toEqual(runtimeConfig);
expect(updateParams.config).toEqual({
...runtimeConfig,
plugins: {
...runtimeConfig.plugins,
installs: previousRecords,
},
});
expect(updateParams.pluginIds).toEqual(["alpha"]);
expect(updateParams.dryRun).toBe(false);
expectInstallRecordsWrittenWithLease(nextConfig.plugins?.installs, {});
expectInstallRecordsWrittenWithLease(nextRecords, cfg);
expect(updateNpmInstalledHookPacksMock).not.toHaveBeenCalled();
expect(configWriteMock).toHaveBeenCalledWith({});
expect(replaceConfigFileMock).toHaveBeenCalledWith({
nextConfig: {},
baseHash: "update-config",
writeOptions: expect.objectContaining({
includeFileHashesForWrite: {
"/tmp/plugins.json5": "plugins-start-hash",
},
}),
});
expect(configWriteMock).not.toHaveBeenCalled();
expect(replaceConfigFileMock).not.toHaveBeenCalled();
expect(failedCommit).toHaveBeenCalledOnce();
expect(remainingCommit).toHaveBeenCalledOnce();
expect(rollback).not.toHaveBeenCalled();
expect(refreshPluginRegistryMock).not.toHaveBeenCalled();
expect(refreshPluginRegistryMock).toHaveBeenCalledWith({
config: cfg,
installRecords: nextRecords,
reason: "source-changed",
});
expect(notifyGatewayPluginMetadataChangedMock).toHaveBeenCalledWith(runtimeConfig);
expect(pluginsCliRuntimeLogs.join("\n")).toContain("Plugin update committed");
expect(pluginsCliRuntimeLogs).toContain("Updated alpha -> 1.1.0");
expect(pluginsCliRuntimeLogs.join("\n")).toContain("Restart is required");
expectRestartNoticeLogged();
});
it("exits non-zero when a plugin update reports an error after persisting successes", async () => {