Skip to content

Commit dd0e4f6

Browse files
brokemac79RomneyDa
andauthored
fix: avoid plugin update range fallback after metadata failure (#96143)
Co-authored-by: Dallin Romney <[email protected]>
1 parent 0da2649 commit dd0e4f6

2 files changed

Lines changed: 108 additions & 3 deletions

File tree

src/plugins/update.test.ts

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1902,7 +1902,7 @@ describe("updateNpmInstalledPlugins", () => {
19021902
});
19031903
});
19041904

1905-
it("falls through to npm reinstall when metadata probing fails", async () => {
1905+
it("falls through to npm reinstall when metadata probing fails for valid specs", async () => {
19061906
const warn = vi.fn();
19071907
const installPath = createInstalledPackageDir({
19081908
name: "@martian-engineering/lossless-claw",
@@ -1937,6 +1937,107 @@ describe("updateNpmInstalledPlugins", () => {
19371937
expect(installPluginFromNpmSpecMock).toHaveBeenCalledTimes(1);
19381938
});
19391939

1940+
it("records range metadata probing failures without falling through to npm reinstall", async () => {
1941+
const warn = vi.fn();
1942+
const installPath = createInstalledPackageDir({
1943+
name: "@martian-engineering/lossless-claw",
1944+
version: "0.9.0",
1945+
});
1946+
runCommandWithTimeoutMock.mockResolvedValueOnce({
1947+
code: 1,
1948+
stdout: "",
1949+
stderr: "registry timeout",
1950+
});
1951+
const result = await updateNpmInstalledPlugins({
1952+
config: createNpmInstallConfig({
1953+
pluginId: "lossless-claw",
1954+
spec: "@martian-engineering/lossless-claw@^0.9.0",
1955+
installPath,
1956+
}),
1957+
pluginIds: ["lossless-claw"],
1958+
logger: { warn },
1959+
});
1960+
1961+
expect(warn).not.toHaveBeenCalled();
1962+
expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled();
1963+
expect(result.changed).toBe(false);
1964+
expect(result.outcomes).toEqual([
1965+
{
1966+
pluginId: "lossless-claw",
1967+
status: "error",
1968+
message: "Failed to check lossless-claw: npm view failed: registry timeout",
1969+
},
1970+
]);
1971+
});
1972+
1973+
it("uses failure cleanup when metadata probing fails and disableOnFailure is enabled", async () => {
1974+
const warn = vi.fn();
1975+
const installPath = createInstalledPackageDir({
1976+
name: "@martian-engineering/lossless-claw",
1977+
version: "0.9.0",
1978+
});
1979+
runCommandWithTimeoutMock.mockResolvedValueOnce({
1980+
code: 1,
1981+
stdout: "",
1982+
stderr: "registry timeout",
1983+
});
1984+
1985+
const result = await updateNpmInstalledPlugins({
1986+
config: {
1987+
plugins: {
1988+
allow: ["lossless-claw", "keep"],
1989+
deny: ["lossless-claw", "blocked"],
1990+
slots: {
1991+
memory: "lossless-claw",
1992+
contextEngine: "lossless-claw",
1993+
},
1994+
entries: {
1995+
"lossless-claw": {
1996+
enabled: true,
1997+
config: { preserved: true },
1998+
},
1999+
},
2000+
installs: {
2001+
"lossless-claw": {
2002+
source: "npm",
2003+
spec: "@martian-engineering/lossless-claw@^0.9.0",
2004+
installPath,
2005+
resolvedName: "@martian-engineering/lossless-claw",
2006+
resolvedVersion: "0.9.0",
2007+
resolvedSpec: "@martian-engineering/[email protected]",
2008+
},
2009+
},
2010+
},
2011+
},
2012+
pluginIds: ["lossless-claw"],
2013+
disableOnFailure: true,
2014+
logger: { warn },
2015+
});
2016+
2017+
const message =
2018+
'Disabled "lossless-claw" after plugin update failure; OpenClaw will continue without it. Failed to check lossless-claw: npm view failed: registry timeout';
2019+
expect(warn).toHaveBeenCalledWith(message);
2020+
expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled();
2021+
expect(result.changed).toBe(true);
2022+
expect(result.config.plugins?.entries?.["lossless-claw"]).toEqual({
2023+
enabled: false,
2024+
config: { preserved: true },
2025+
});
2026+
expect(result.config.plugins?.allow).toEqual(["keep"]);
2027+
expect(result.config.plugins?.deny).toEqual(["blocked"]);
2028+
expect(result.config.plugins?.slots).toEqual({
2029+
memory: "memory-core",
2030+
contextEngine: "legacy",
2031+
});
2032+
expect(result.outcomes).toEqual([
2033+
{
2034+
pluginId: "lossless-claw",
2035+
status: "skipped",
2036+
message,
2037+
},
2038+
]);
2039+
});
2040+
19402041
it.each([
19412042
{
19422043
source: "npm",
@@ -3864,6 +3965,7 @@ describe("updateNpmInstalledPlugins", () => {
38643965
it("reuses the recorded managed extensions root when updating external plugins", async () => {
38653966
const installPath = "/var/openclaw/extensions/demo";
38663967
const extensionsDir = "/var/openclaw/extensions";
3968+
const expectedExtensionsDir = path.resolve(extensionsDir);
38673969
installPluginFromNpmSpecMock.mockResolvedValue(
38683970
createSuccessfulNpmUpdateResult({
38693971
pluginId: "demo",
@@ -3947,7 +4049,6 @@ describe("updateNpmInstalledPlugins", () => {
39474049
pluginIds: ["demo"],
39484050
});
39494051

3950-
const expectedExtensionsDir = path.resolve(extensionsDir);
39514052
expect(npmInstallCall()?.extensionsDir).toBe(expectedExtensionsDir);
39524053
expect(clawHubInstallCall()?.extensionsDir).toBe(expectedExtensionsDir);
39534054
expect(marketplaceInstallCall()?.extensionsDir).toBe(expectedExtensionsDir);

src/plugins/update.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ function resolveRecordedExtensionsDir(params: {
493493
const parentDir = path.dirname(params.installPath);
494494
try {
495495
const canonicalInstallPath = resolvePluginInstallDir(params.pluginId, parentDir);
496-
return canonicalInstallPath === params.installPath ? parentDir : undefined;
496+
return pathsEqual(canonicalInstallPath, params.installPath) ? parentDir : undefined;
497497
} catch {
498498
return undefined;
499499
}
@@ -1584,6 +1584,10 @@ export async function updateNpmInstalledPlugins(params: {
15841584
continue;
15851585
}
15861586
} else {
1587+
if (!parseRegistryNpmSpec(effectiveSpec!)) {
1588+
recordFailure(pluginId, `Failed to check ${pluginId}: ${metadataResult.error}`);
1589+
continue;
1590+
}
15871591
logger.warn?.(
15881592
`Could not check ${pluginId} before update; falling back to installer path: ${metadataResult.error}`,
15891593
);

0 commit comments

Comments
 (0)