Skip to content

Commit b8ea609

Browse files
BKF-Gittyaltaywtf
andauthored
fix(cli): report stale plugin doctor config (#81515)
Merged via squash. Prepared head SHA: 23bc849 Co-authored-by: BKF-Gitty <[email protected]> Co-authored-by: altaywtf <[email protected]> Reviewed-by: @altaywtf
1 parent 4d2e708 commit b8ea609

4 files changed

Lines changed: 91 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ Docs: https://docs.openclaw.ai
104104
- Test state: seed isolated auth-profile secret keys for generated homes, preventing helper-backed proof runs from falling back to host Keychain secrets. (#81393) Thanks @altaywtf.
105105
- Plugins/runtime: attribute deprecated runtime config load/write warnings to the plugin id and source that triggered them so logs and plugin doctor runs are actionable. Refs #81394. (#81425) Thanks @BKF-Gitty.
106106
- Memory/LanceDB: make auto-capture recognize short CJK memory phrases and configurable literal triggers, so Chinese, Japanese, and Korean users can capture memories without regex or LLM intent detection. Fixes #75680. Thanks @vyctorbrzezowski and @guokewuming.
107+
- Plugins doctor: report stale plugin config warnings and avoid claiming full plugin health when config warnings remain. (#81515) Thanks @BKF-Gitty.
107108

108109
### Changes
109110

docs/cli/plugins.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ The `--json` flag outputs a machine-readable report suitable for scripting and a
386386
openclaw plugins doctor
387387
```
388388

389-
`doctor` reports plugin load errors, manifest/discovery diagnostics, and compatibility notices. When everything is clean it prints `No plugin issues detected.`
389+
`doctor` reports plugin load errors, manifest/discovery diagnostics, compatibility notices, and stale plugin config references such as missing plugin slots. When the install tree and plugin config are clean it prints `No plugin issues detected.` If stale config remains but the install tree is otherwise healthy, the summary says so instead of implying full plugin health.
390390

391391
If a configured plugin is present on disk but blocked by the loader's path-safety checks, config validation keeps the plugin entry and reports it as `present but blocked`. Fix the preceding blocked-plugin diagnostic, such as path ownership or world-writable permissions, instead of removing the `plugins.entries.<id>` or `plugins.allow` config.
392392

src/cli/plugins-cli.list.test.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
buildPluginRegistrySnapshotReport,
77
buildPluginSnapshotReport,
88
inspectPluginRegistry,
9+
loadConfig,
10+
readConfigFileSnapshot,
911
resetPluginsCliTestState,
1012
refreshPluginRegistry,
1113
runPluginsCommand,
@@ -79,10 +81,63 @@ describe("plugins cli list", () => {
7981

8082
await runPluginsCommand(["plugins", "doctor"]);
8183

82-
expect(buildPluginDiagnosticsReport).toHaveBeenCalledWith({ effectiveOnly: true });
84+
expect(buildPluginDiagnosticsReport).toHaveBeenCalledWith({ config: {}, effectiveOnly: true });
8385
expect(runtimeLogs).toContain("No plugin issues detected.");
8486
});
8587

88+
it("reports stale plugin config in doctor output without claiming full plugin health", async () => {
89+
const sourceConfig = {
90+
plugins: {
91+
allow: ["lossless-claw"],
92+
entries: {
93+
"lossless-claw": { enabled: true },
94+
},
95+
slots: {
96+
contextEngine: "lossless-claw",
97+
},
98+
},
99+
};
100+
loadConfig.mockReturnValue({});
101+
readConfigFileSnapshot.mockResolvedValueOnce({
102+
path: "/tmp/openclaw-config.json5",
103+
exists: true,
104+
raw: "{}",
105+
parsed: sourceConfig,
106+
resolved: sourceConfig,
107+
sourceConfig,
108+
runtimeConfig: {},
109+
config: {},
110+
valid: true,
111+
hash: "mock",
112+
issues: [],
113+
warnings: [],
114+
legacyIssues: [],
115+
});
116+
buildPluginDiagnosticsReport.mockReturnValue({
117+
plugins: [],
118+
diagnostics: [],
119+
});
120+
121+
await runPluginsCommand(["plugins", "doctor"]);
122+
123+
const output = runtimeLogs.join("\n");
124+
expect(output).toContain("Plugin configuration:");
125+
expect(output).toContain('plugins.allow: stale plugin reference "lossless-claw" was found.');
126+
expect(output).toContain(
127+
'plugins.entries.lossless-claw: stale plugin reference "lossless-claw" was found.',
128+
);
129+
expect(output).toContain(
130+
'plugins.slots.contextEngine: slot references missing plugin "lossless-claw".',
131+
);
132+
expect(output).toContain(
133+
'Run "openclaw doctor --fix" to remove stale plugin ids and dangling channel references.',
134+
);
135+
expect(output).toContain(
136+
"No plugin install-tree issues detected; configuration warnings remain.",
137+
);
138+
expect(output).not.toContain("No plugin issues detected.");
139+
});
140+
86141
it("reports config-selected plugin source shadowing in doctor output", async () => {
87142
buildPluginDiagnosticsReport.mockReturnValue({
88143
plugins: [

src/cli/plugins-cli.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -365,20 +365,33 @@ export function registerPluginsCli(program: Command) {
365365
buildPluginDiagnosticsReport,
366366
formatPluginCompatibilityNotice,
367367
} = await import("../plugins/status.js");
368-
const report = buildPluginDiagnosticsReport({ effectiveOnly: true });
368+
const {
369+
collectStalePluginConfigWarnings,
370+
isStalePluginAutoRepairBlocked,
371+
scanStalePluginConfig,
372+
} = await import("../commands/doctor/shared/stale-plugin-config.js");
373+
const cfg = getRuntimeConfig();
374+
const configSnapshot = await readConfigFileSnapshot().catch(() => null);
375+
const sourceCfg = (configSnapshot?.sourceConfig ?? configSnapshot?.config ?? cfg) as
376+
| OpenClawConfig
377+
| undefined;
378+
const report = buildPluginDiagnosticsReport({ config: cfg, effectiveOnly: true });
369379
const errors = report.plugins.filter((p) => p.status === "error");
370380
const diags = report.diagnostics.filter((d) => d.level === "error");
371381
const shadowed = report.diagnostics.filter((entry) =>
372382
isErroredConfigSelectedShadowDiagnostic({ entry, plugins: report.plugins }),
373383
);
374384
const compatibility = buildPluginCompatibilityNotices({ report });
385+
const stalePluginConfigHits = scanStalePluginConfig(sourceCfg ?? cfg, process.env);
386+
const stalePluginConfigWarnings = collectStalePluginConfigWarnings({
387+
hits: stalePluginConfigHits,
388+
doctorFixCommand: "openclaw doctor --fix",
389+
autoRepairBlocked: isStalePluginAutoRepairBlocked(sourceCfg ?? cfg, process.env),
390+
});
391+
const hasInstallTreeIssues =
392+
errors.length > 0 || diags.length > 0 || shadowed.length > 0 || compatibility.length > 0;
375393

376-
if (
377-
errors.length === 0 &&
378-
diags.length === 0 &&
379-
shadowed.length === 0 &&
380-
compatibility.length === 0
381-
) {
394+
if (!hasInstallTreeIssues && stalePluginConfigWarnings.length === 0) {
382395
defaultRuntime.log("No plugin issues detected.");
383396
return;
384397
}
@@ -436,6 +449,19 @@ export function registerPluginsCli(program: Command) {
436449
lines.push(`- ${formatPluginCompatibilityNotice(notice)} [${marker}]`);
437450
}
438451
}
452+
if (stalePluginConfigWarnings.length > 0) {
453+
if (lines.length > 0) {
454+
lines.push("");
455+
}
456+
lines.push(theme.warn("Plugin configuration:"));
457+
lines.push(...stalePluginConfigWarnings);
458+
}
459+
if (!hasInstallTreeIssues && stalePluginConfigWarnings.length > 0) {
460+
if (lines.length > 0) {
461+
lines.push("");
462+
}
463+
lines.push("No plugin install-tree issues detected; configuration warnings remain.");
464+
}
439465
const docs = formatDocsLink("/plugin", "docs.openclaw.ai/plugin");
440466
lines.push("");
441467
lines.push(`${theme.muted("Docs:")} ${docs}`);

0 commit comments

Comments
 (0)