fix: tighten stale plugin diagnostic registry checks (#80134)

This commit is contained in:
Peter Steinberger
2026-05-10 07:52:34 +01:00
parent d160f82719
commit 9e7acd4b2b
3 changed files with 54 additions and 3 deletions

View File

@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Media/host-read: allow buffer-verified gzip, tar, and 7z archives in the shared host-local media validator alongside ZIP and document attachments.
- Plugins/doctor: invalidate persisted plugin registry snapshots when plugin diagnostics point at deleted source paths, so `openclaw doctor` stops repeating stale warnings after a local extension is replaced by a managed npm plugin. Fixes #80087. (#80134) Thanks @hclsys.
- Cron: let isolated self-cleanup runs inspect their own job run history while keeping other cron jobs and mutation actions blocked. Fixes #80019. Thanks @hclsys.
- Cron: report isolated agent-turn setup and pre-model stalls with phase-specific timeout errors instead of waiting for the full job budget when no model call starts. Fixes #74803. Thanks @jeffsteinbok-openclaw and @dgkim311.
- CLI/config: persist explicit `config set` and `config patch` values that equal runtime defaults instead of reporting success while dropping them. Fixes #79856. (#80106) Thanks @abodanty and @hclsys.

View File

@@ -309,12 +309,22 @@ describe("loadPluginRegistrySnapshotWithMetadata", () => {
);
});
it("treats persisted registry as stale when a diagnostic source path no longer exists", () => {
it("treats persisted registry as stale when a plugin diagnostic source path no longer exists", () => {
const tempRoot = makeTempDir();
const stateDir = path.join(tempRoot, "state");
const env = { ...createHermeticEnv(tempRoot), OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1" };
const env = {
...createHermeticEnv(tempRoot),
OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
OPENCLAW_STATE_DIR: stateDir,
};
const config = {};
const ghostDir = path.join(tempRoot, "extensions", "lossless-claw");
const npmPluginDir = writeManagedNpmPlugin({
stateDir,
packageName: "@martian-engineering/lossless-claw",
pluginId: "lossless-claw",
version: "0.9.4",
});
const staleIndex: InstalledPluginIndex = {
...loadInstalledPluginIndex({ config, env, stateDir, installRecords: {} }),
diagnostics: [
@@ -335,8 +345,42 @@ describe("loadPluginRegistrySnapshotWithMetadata", () => {
expect(result.snapshot.diagnostics).not.toContainEqual(
expect.objectContaining({ source: ghostDir }),
);
expect(result.snapshot.plugins).toContainEqual(
expect.objectContaining({
pluginId: "lossless-claw",
origin: "global",
source: fs.realpathSync(path.join(npmPluginDir, "dist", "index.js")),
}),
);
expect(result.diagnostics).toContainEqual(
expect.objectContaining({ code: "persisted-registry-stale-source" }),
);
});
it("keeps persisted registry when a non-plugin diagnostic source path still does not exist", () => {
const tempRoot = makeTempDir();
const stateDir = path.join(tempRoot, "state");
const env = { ...createHermeticEnv(tempRoot), OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1" };
const config = {};
const missingConfiguredPath = path.join(tempRoot, "missing-configured-plugin");
const index: InstalledPluginIndex = {
...loadInstalledPluginIndex({ config, env, stateDir, installRecords: {} }),
diagnostics: [
{
level: "error",
message: `plugin path not found: ${missingConfiguredPath}`,
source: missingConfiguredPath,
},
],
};
writePersistedInstalledPluginIndexSync(index, { stateDir });
const result = loadPluginRegistrySnapshotWithMetadata({ config, env, stateDir });
expect(result.source).toBe("persisted");
expect(result.snapshot.diagnostics).toContainEqual(
expect.objectContaining({ source: missingConfiguredPath }),
);
expect(result.diagnostics).toStrictEqual([]);
});
});

View File

@@ -134,7 +134,13 @@ function resolveRecordPackageJsonPath(plugin: InstalledPluginIndexRecord): strin
function hasStalePersistedPluginDiagnostics(index: InstalledPluginIndex): boolean {
return index.diagnostics.some((diag) => {
const source = diag.source;
return typeof source === "string" && path.isAbsolute(source) && !fs.existsSync(source);
return (
typeof diag.pluginId === "string" &&
diag.pluginId.trim().length > 0 &&
typeof source === "string" &&
path.isAbsolute(source) &&
!fs.existsSync(source)
);
});
}