From 0131343db8c613ecdbd8853af8044153baa74c04 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 4 May 2026 18:16:29 -0700 Subject: [PATCH] docs(doctor): clarify configured plugin repair (#77613) --- docs/cli/doctor.md | 2 +- docs/cli/plugins.md | 2 +- docs/gateway/doctor.md | 2 +- docs/plugins/bundles.md | 4 ++-- docs/plugins/dependency-resolution.md | 7 ++++--- src/commands/doctor/repair-sequencing.test.ts | 4 ++-- .../shared/missing-configured-plugin-install.test.ts | 4 ++-- .../doctor/shared/missing-configured-plugin-install.ts | 2 +- .../shared/release-configured-plugin-installs.test.ts | 4 ++-- src/flows/doctor-health-contributions.ts | 2 +- src/plugins/conversation-binding.ts | 2 +- src/plugins/provider-auth-choice.ts | 2 +- 12 files changed, 19 insertions(+), 18 deletions(-) diff --git a/docs/cli/doctor.md b/docs/cli/doctor.md index e22c755cbf6..a240aa11c12 100644 --- a/docs/cli/doctor.md +++ b/docs/cli/doctor.md @@ -45,7 +45,7 @@ Notes: - State integrity checks now detect orphan transcript files in the sessions directory. Archiving them as `.deleted.` requires an interactive confirmation; `--fix`, `--yes`, and headless runs leave them in place. - Doctor also scans `~/.openclaw/cron/jobs.json` (or `cron.store`) for legacy cron job shapes and can rewrite them in place before the scheduler has to auto-normalize them at runtime. - On Linux, doctor warns when the user's crontab still runs legacy `~/.openclaw/bin/ensure-whatsapp.sh`; that script is no longer maintained and can log false WhatsApp gateway outages when cron lacks the systemd user-bus environment. -- Doctor cleans legacy plugin dependency staging state created by older OpenClaw versions. It also repairs missing configured downloadable plugins when the registry can resolve them, and the 2026.5.2 doctor pass automatically installs downloadable plugins that an older config already uses before marking the config touched for that release. If the download fails, doctor reports the install error and preserves the configured plugin entry for the next repair attempt. +- Doctor cleans legacy plugin dependency staging state created by older OpenClaw versions. It also repairs missing downloadable plugins that are referenced by config, such as `plugins.entries`, configured channels, configured provider/search settings, or configured agent runtimes. During package updates, doctor skips package-manager plugin repair until the package swap is complete; rerun `openclaw doctor --fix` afterward if a configured plugin still needs recovery. If the download fails, doctor reports the install error and preserves the configured plugin entry for the next repair attempt. - Doctor repairs stale plugin config by removing missing plugin ids from `plugins.allow`/`plugins.entries`, plus matching dangling channel config, heartbeat targets, and channel model overrides when plugin discovery is healthy. - Doctor quarantines invalid plugin config by disabling the affected `plugins.entries.` entry and removing its invalid `config` payload. Gateway startup already skips only that bad plugin so other plugins and channels can keep running. - Set `OPENCLAW_SERVICE_REPAIR_POLICY=external` when another supervisor owns the gateway lifecycle. Doctor still reports gateway/service health and applies non-service repairs, but skips service install/start/restart/bootstrap and legacy service cleanup. diff --git a/docs/cli/plugins.md b/docs/cli/plugins.md index 9d98c22ec80..2467aad1f61 100644 --- a/docs/cli/plugins.md +++ b/docs/cli/plugins.md @@ -266,7 +266,7 @@ directory remains inert so normal packaged installs still use compiled dist. For runtime hook debugging: -- `openclaw plugins inspect --runtime --json` shows registered hooks and diagnostics from a module-loaded inspection pass. Runtime inspection never installs dependencies; use `openclaw doctor --fix` to clean legacy dependency state or install missing configured downloadable plugins. +- `openclaw plugins inspect --runtime --json` shows registered hooks and diagnostics from a module-loaded inspection pass. Runtime inspection never installs dependencies; use `openclaw doctor --fix` to clean legacy dependency state or recover missing downloadable plugins that are referenced by config. - `openclaw gateway status --deep --require-rpc` confirms the reachable Gateway, service/process hints, config path, and RPC health. - Non-bundled conversation hooks (`llm_input`, `llm_output`, `before_agent_finalize`, `agent_end`) require `plugins.entries..hooks.allowConversationAccess=true`. diff --git a/docs/gateway/doctor.md b/docs/gateway/doctor.md index 3382097ad8d..360a4653d34 100644 --- a/docs/gateway/doctor.md +++ b/docs/gateway/doctor.md @@ -355,7 +355,7 @@ That stages grounded durable candidates into the short-term dreaming store while Doctor removes legacy OpenClaw-generated plugin dependency staging state in `openclaw doctor --fix` / `openclaw doctor --repair` mode. This covers stale generated dependency roots, old install-stage directories, package-local debris from earlier bundled-plugin dependency repair code, and orphaned or recovered managed npm copies of bundled `@openclaw/*` plugins that can shadow the current bundled manifest. - Doctor can also reinstall configured downloadable plugins when the config references them but the local plugin registry cannot find them. For the 2026.5.2 bundled-plugin externalization, doctor automatically installs downloadable plugins that the existing config already uses and then relies on `meta.lastTouchedVersion` to run that release pass only once. Gateway startup and config reload do not run package managers; plugin installs remain explicit doctor/install/update work. + Doctor can also reinstall missing downloadable plugins when config references them but the local plugin registry cannot find them. Examples include material `plugins.entries`, configured channel/provider/search settings, and configured agent runtimes. During package updates, doctor avoids running package-manager plugin repair while the core package is being swapped; run `openclaw doctor --fix` again after the update if a configured plugin still needs recovery. Gateway startup and config reload do not run package managers; plugin installs remain explicit doctor/install/update work. diff --git a/docs/plugins/bundles.md b/docs/plugins/bundles.md index 54dc2273fe4..ab6d376d1d5 100644 --- a/docs/plugins/bundles.md +++ b/docs/plugins/bundles.md @@ -262,8 +262,8 @@ dual-format packages from being partially installed as bundles. downloadable through the plugin installer. Gateway startup never runs a package manager for them. - `openclaw doctor --fix` removes legacy staged dependency directories and can - install configured downloadable plugins that are missing from the local - plugin index. + recover downloadable plugins that are missing from the local plugin index when + config references them. ## Security diff --git a/docs/plugins/dependency-resolution.md b/docs/plugins/dependency-resolution.md index 45838efcc0c..1e6e0410161 100644 --- a/docs/plugins/dependency-resolution.md +++ b/docs/plugins/dependency-resolution.md @@ -85,9 +85,10 @@ openclaw plugins install openclaw doctor --fix ``` -`doctor --fix` can clean legacy OpenClaw-generated dependency state and install -configured downloadable plugins that are missing from the local install records. -It does not repair dependencies for an already-installed local plugin. +`doctor --fix` can clean legacy OpenClaw-generated dependency state and recover +downloadable plugins that are missing from the local install records when config +references them. Doctor does not repair dependencies for an already-installed +local plugin. ## Bundled plugins diff --git a/src/commands/doctor/repair-sequencing.test.ts b/src/commands/doctor/repair-sequencing.test.ts index 2c828889cc8..5537507e808 100644 --- a/src/commands/doctor/repair-sequencing.test.ts +++ b/src/commands/doctor/repair-sequencing.test.ts @@ -363,7 +363,7 @@ describe("doctor repair sequencing", () => { it("does not remove deferred configured plugins during the package update doctor pass", async () => { mocks.repairMissingConfiguredPluginInstalls.mockResolvedValueOnce({ changes: [ - 'Deferred missing configured plugin "brave" install repair until post-update doctor.', + 'Skipped package-manager repair for configured plugin "brave" during package update; rerun "openclaw doctor --fix" after the update completes.', ], warnings: [], }); @@ -432,7 +432,7 @@ describe("doctor repair sequencing", () => { expect(result.state.candidate.plugins?.allow).toEqual(["brave"]); expect(result.state.candidate.plugins?.entries?.brave?.enabled).toBe(true); expect(result.changeNotes).toContain( - 'Deferred missing configured plugin "brave" install repair until post-update doctor.', + 'Skipped package-manager repair for configured plugin "brave" during package update; rerun "openclaw doctor --fix" after the update completes.', ); }); diff --git a/src/commands/doctor/shared/missing-configured-plugin-install.test.ts b/src/commands/doctor/shared/missing-configured-plugin-install.test.ts index 9cdd56e9ef9..ba649aa7ed4 100644 --- a/src/commands/doctor/shared/missing-configured-plugin-install.test.ts +++ b/src/commands/doctor/shared/missing-configured-plugin-install.test.ts @@ -917,7 +917,7 @@ describe("repairMissingConfiguredPluginInstalls", () => { ); expect(result).toEqual({ changes: [ - 'Deferred missing configured plugin "discord" install repair until post-update doctor.', + 'Skipped package-manager repair for configured plugin "discord" during package update; rerun "openclaw doctor --fix" after the update completes.', ], warnings: [], }); @@ -969,7 +969,7 @@ describe("repairMissingConfiguredPluginInstalls", () => { ); expect(result).toEqual({ changes: [ - 'Deferred missing configured plugin "discord" install repair until post-update doctor.', + 'Skipped package-manager repair for configured plugin "discord" during package update; rerun "openclaw doctor --fix" after the update completes.', ], warnings: [], }); diff --git a/src/commands/doctor/shared/missing-configured-plugin-install.ts b/src/commands/doctor/shared/missing-configured-plugin-install.ts index 8ed0ee3d90c..4542893e39e 100644 --- a/src/commands/doctor/shared/missing-configured-plugin-install.ts +++ b/src/commands/doctor/shared/missing-configured-plugin-install.ts @@ -676,7 +676,7 @@ async function repairMissingPluginInstalls(params: { } delete nextRecords[pluginId]; changes.push( - `Deferred missing configured plugin "${pluginId}" install repair until post-update doctor.`, + `Skipped package-manager repair for configured plugin "${pluginId}" during package update; rerun "openclaw doctor --fix" after the update completes.`, ); } } diff --git a/src/commands/doctor/shared/release-configured-plugin-installs.test.ts b/src/commands/doctor/shared/release-configured-plugin-installs.test.ts index eb98948ce7d..69ffdb7cda7 100644 --- a/src/commands/doctor/shared/release-configured-plugin-installs.test.ts +++ b/src/commands/doctor/shared/release-configured-plugin-installs.test.ts @@ -284,7 +284,7 @@ describe("configured plugin install release step", () => { it("does not stamp config during update-time deferred install repair", async () => { mocks.repairMissingPluginInstallsForIds.mockResolvedValue({ changes: [ - 'Deferred missing configured plugin "codex" install repair until post-update doctor.', + 'Skipped package-manager repair for configured plugin "codex" during package update; rerun "openclaw doctor --fix" after the update completes.', ], warnings: [], }); @@ -313,7 +313,7 @@ describe("configured plugin install release step", () => { ); expect(result).toEqual({ changes: [ - 'Deferred missing configured plugin "codex" install repair until post-update doctor.', + 'Skipped package-manager repair for configured plugin "codex" during package update; rerun "openclaw doctor --fix" after the update completes.', ], warnings: [], completed: false, diff --git a/src/flows/doctor-health-contributions.ts b/src/flows/doctor-health-contributions.ts index 8b64d9a1ef0..a6f6f1aed08 100644 --- a/src/flows/doctor-health-contributions.ts +++ b/src/flows/doctor-health-contributions.ts @@ -650,7 +650,7 @@ export function resolveDoctorHealthContributions(): DoctorHealthContribution[] { }), createDoctorHealthContribution({ id: "doctor:release-configured-plugin-installs", - label: "Configured plugin installs", + label: "Configured plugin repair", run: runReleaseConfiguredPluginInstallsHealth, }), createDoctorHealthContribution({ diff --git a/src/plugins/conversation-binding.ts b/src/plugins/conversation-binding.ts index 8b124fecdb3..558ce4a0e2f 100644 --- a/src/plugins/conversation-binding.ts +++ b/src/plugins/conversation-binding.ts @@ -651,7 +651,7 @@ function buildDetachHintSuffix(detachHint?: string): string { } export function buildPluginBindingUnavailableText(binding: PluginConversationBinding): string { - return `The bound plugin ${resolvePluginBindingDisplayName(binding)} is not currently loaded. Routing this message to OpenClaw instead.${buildDetachHintSuffix(binding.detachHint)}`; + return `The bound plugin ${resolvePluginBindingDisplayName(binding)} is not currently loaded. Routing this message to OpenClaw instead. If this started after an update, run "openclaw doctor --fix"; otherwise reinstall or enable the plugin.${buildDetachHintSuffix(binding.detachHint)}`; } export function buildPluginBindingDeclinedText(binding: PluginConversationBinding): string { diff --git a/src/plugins/provider-auth-choice.ts b/src/plugins/provider-auth-choice.ts index 2b17d83c755..497735fad7d 100644 --- a/src/plugins/provider-auth-choice.ts +++ b/src/plugins/provider-auth-choice.ts @@ -487,7 +487,7 @@ export async function applyAuthChoicePluginProvider( const provider = resolveProviderMatch(providers, options.providerId); if (!provider) { await params.prompter.note( - `${options.label} auth plugin is not available. Enable it and re-run onboarding.`, + `${options.label} auth plugin is not available. Install or enable the plugin, then rerun onboarding. If this started after an update, run "openclaw doctor --fix" first.`, options.label, ); return { config: nextConfig };