openclaw - 💡(How to fix) Fix doctor --fix cannot recover missing bundled runtime deps when config preflight collapses invalid snapshot to {} [2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#70123Fetched 2026-04-23 07:29:01
View on GitHub
Comments
2
Participants
2
Timeline
3
Reactions
0
Author
Participants
Timeline (top)
commented ×2closed ×1

openclaw doctor --fix can get stuck in a self-locking failure path when a bundled plugin runtime dependency is missing.

In my case, feishu was explicitly enabled, but @larksuiteoapi/node-sdk was missing from the installed packaged runtime. doctor --fix reported:

Invalid config:
- <root>: read failed: Error: Cannot find module '@larksuiteoapi/node-sdk'
Require stack:
- .../openclaw/dist/extensions/feishu/client-yWGpoGkZ.js

and then completed without ever running the bundled runtime dependency repair for Feishu.

The important part is that this is not just “missing bundled deps after upgrade” again. The specific bug here is:

  • config preflight says doctor will use a “best-effort config”
  • but the preflight path actually returns {} after the plugin import failure
  • bundled runtime dep repair then scans an empty config and decides Feishu is not enabled
  • so doctor --fix never installs the missing dependency that caused the failure in the first place

This makes the recovery path self-locking.

Error Message

Invalid config:

  • <root>: read failed: Error: Cannot find module '@larksuiteoapi/node-sdk' Require stack:
  • .../openclaw/dist/extensions/feishu/client-yWGpoGkZ.js

Root Cause

The failure chain appears to be:

  1. runDoctorConfigPreflight() calls readConfigFileSnapshot().
  2. Config validation/import fails while loading Feishu because @larksuiteoapi/node-sdk is missing.
  3. The invalid snapshot path returns:
    • parsed: {}
    • sourceConfig: {}
    • runtimeConfig: {}
  4. runDoctorConfigPreflight() then returns:
    • baseConfig: snapshot.sourceConfig ?? snapshot.config ?? {}
  5. Because snapshot.sourceConfig is already {} instead of null, doctor proceeds with an empty config rather than a source-level best-effort config.
  6. runBundledPluginRuntimeDepsHealth() passes that empty ctx.cfg into bundled runtime dep scanning.
  7. scanBundledPluginRuntimeDeps() / isBundledPluginConfiguredForRuntimeDeps() then do not see Feishu as enabled, so they skip it.
  8. The dependency that caused the failure is never installed.

I verified locally that the scanner itself is fine when given the real config: with the actual config loaded, scanBundledPluginRuntimeDeps() correctly reports:

{
  "missing": [
    {
      "name": "@larksuiteoapi/node-sdk",
      "version": "^1.60.0",
      "pluginIds": ["feishu"]
    }
  ],
  "conflicts": []
}

So the bug is not in the scanner. The bug is that doctor hands it the wrong config after preflight failure.

Fix Action

Fix / Workaround

Recommended patch

That may still work in some cases because Node can resolve upward, but it diverges from the loader/runtime path and from the manual workaround that actually repaired my environment. I am not sure whether you want to fix that in this issue or separately, but the preflight/base-config bug above is enough to explain the self-lock.

Code Example

Invalid config:
- <root>: read failed: Error: Cannot find module '@larksuiteoapi/node-sdk'
Require stack:
- .../openclaw/dist/extensions/feishu/client-yWGpoGkZ.js

---

openclaw doctor --fix --non-interactive

---

cd "$(npm root -g)/openclaw/dist/extensions/feishu"
npm install --no-audit --no-fund

---

{
  "missing": [
    {
      "name": "@larksuiteoapi/node-sdk",
      "version": "^1.60.0",
      "pluginIds": ["feishu"]
    }
  ],
  "conflicts": []
}

---

diff --git a/src/commands/doctor-config-preflight.ts b/src/commands/doctor-config-preflight.ts
index ???????..??????? 100644
--- a/src/commands/doctor-config-preflight.ts
+++ b/src/commands/doctor-config-preflight.ts
@@
-import { readConfigFileSnapshot } from "../config/io";
+import { readConfigFileSnapshot, readSourceConfigBestEffort } from "../config/io";
@@
   const snapshot = await readConfigFileSnapshot();
   const invalidConfigNote = options.invalidConfigNote ?? "Config invalid; doctor will run with best-effort config.";
   if (invalidConfigNote && snapshot.exists && !snapshot.valid && snapshot.legacyIssues.length === 0) {
     note(invalidConfigNote, "Config");
     noteIncludeConfinementWarning(snapshot);
   }
   const warnings = snapshot.warnings ?? [];
   if (warnings.length > 0) note(formatConfigIssueLines(warnings, "-").join("\n"), "Config warnings");
+  const baseConfig = snapshot.valid
+    ? (snapshot.sourceConfig ?? snapshot.config ?? {})
+    : await readSourceConfigBestEffort();
   return {
     snapshot,
-    baseConfig: snapshot.sourceConfig ?? snapshot.config ?? {}
+    baseConfig
   };
 }
RAW_BUFFERClick to expand / collapse

Summary

openclaw doctor --fix can get stuck in a self-locking failure path when a bundled plugin runtime dependency is missing.

In my case, feishu was explicitly enabled, but @larksuiteoapi/node-sdk was missing from the installed packaged runtime. doctor --fix reported:

Invalid config:
- <root>: read failed: Error: Cannot find module '@larksuiteoapi/node-sdk'
Require stack:
- .../openclaw/dist/extensions/feishu/client-yWGpoGkZ.js

and then completed without ever running the bundled runtime dependency repair for Feishu.

The important part is that this is not just “missing bundled deps after upgrade” again. The specific bug here is:

  • config preflight says doctor will use a “best-effort config”
  • but the preflight path actually returns {} after the plugin import failure
  • bundled runtime dep repair then scans an empty config and decides Feishu is not enabled
  • so doctor --fix never installs the missing dependency that caused the failure in the first place

This makes the recovery path self-locking.

Environment

  • OpenClaw: 2026.4.21
  • Node: v22.22.0
  • Install type: packaged global install under npm/nvm
  • Reproduced on macOS locally
  • Likely not macOS-specific; the failure is in doctor/config preflight logic

Reproduction

  1. Install or upgrade a packaged OpenClaw build where bundled runtime deps are not fully present.
  2. Ensure Feishu is explicitly enabled in config, for example:
    • channels.feishu.enabled=true
    • plugins.entries.feishu.enabled=true
  3. Confirm dist/extensions/feishu/node_modules/@larksuiteoapi/node-sdk is missing.
  4. Run:
openclaw doctor --fix --non-interactive

Actual result

doctor --fix reports the invalid config / missing module error and completes, but it does not print the bundled-runtime-deps health note for Feishu and does not install @larksuiteoapi/node-sdk.

In my local repro, the only thing that fixed the environment was manually installing in the plugin runtime dir:

cd "$(npm root -g)/openclaw/dist/extensions/feishu"
npm install --no-audit --no-fund

After that:

  • direct import of dist/extensions/feishu/client-*.js succeeded
  • openclaw doctor --non-interactive no longer failed on @larksuiteoapi/node-sdk

Expected result

When config validation fails because an enabled bundled plugin is missing a runtime dependency, openclaw doctor --fix should still be able to infer from the raw source config that the plugin/channel is enabled and repair its missing runtime deps before trying to fully validate/import it again.

Root cause

The failure chain appears to be:

  1. runDoctorConfigPreflight() calls readConfigFileSnapshot().
  2. Config validation/import fails while loading Feishu because @larksuiteoapi/node-sdk is missing.
  3. The invalid snapshot path returns:
    • parsed: {}
    • sourceConfig: {}
    • runtimeConfig: {}
  4. runDoctorConfigPreflight() then returns:
    • baseConfig: snapshot.sourceConfig ?? snapshot.config ?? {}
  5. Because snapshot.sourceConfig is already {} instead of null, doctor proceeds with an empty config rather than a source-level best-effort config.
  6. runBundledPluginRuntimeDepsHealth() passes that empty ctx.cfg into bundled runtime dep scanning.
  7. scanBundledPluginRuntimeDeps() / isBundledPluginConfiguredForRuntimeDeps() then do not see Feishu as enabled, so they skip it.
  8. The dependency that caused the failure is never installed.

I verified locally that the scanner itself is fine when given the real config: with the actual config loaded, scanBundledPluginRuntimeDeps() correctly reports:

{
  "missing": [
    {
      "name": "@larksuiteoapi/node-sdk",
      "version": "^1.60.0",
      "pluginIds": ["feishu"]
    }
  ],
  "conflicts": []
}

So the bug is not in the scanner. The bug is that doctor hands it the wrong config after preflight failure.

Recommended patch

The simplest fix looks like: if readConfigFileSnapshot() returns an invalid snapshot, preflight should fall back to readSourceConfigBestEffort() instead of using the already-collapsed {} snapshot config.

Suggested diff

diff --git a/src/commands/doctor-config-preflight.ts b/src/commands/doctor-config-preflight.ts
index ???????..??????? 100644
--- a/src/commands/doctor-config-preflight.ts
+++ b/src/commands/doctor-config-preflight.ts
@@
-import { readConfigFileSnapshot } from "../config/io";
+import { readConfigFileSnapshot, readSourceConfigBestEffort } from "../config/io";
@@
   const snapshot = await readConfigFileSnapshot();
   const invalidConfigNote = options.invalidConfigNote ?? "Config invalid; doctor will run with best-effort config.";
   if (invalidConfigNote && snapshot.exists && !snapshot.valid && snapshot.legacyIssues.length === 0) {
     note(invalidConfigNote, "Config");
     noteIncludeConfinementWarning(snapshot);
   }
   const warnings = snapshot.warnings ?? [];
   if (warnings.length > 0) note(formatConfigIssueLines(warnings, "-").join("\n"), "Config warnings");
+  const baseConfig = snapshot.valid
+    ? (snapshot.sourceConfig ?? snapshot.config ?? {})
+    : await readSourceConfigBestEffort();
   return {
     snapshot,
-    baseConfig: snapshot.sourceConfig ?? snapshot.config ?? {}
+    baseConfig
   };
 }

Secondary observation

While tracing this, I also noticed a design inconsistency:

  • loader/runtime repair installs bundled deps into each plugin runtime dir
  • doctor's bundled-runtime-deps repair path appears to install into the package root instead

That may still work in some cases because Node can resolve upward, but it diverges from the loader/runtime path and from the manual workaround that actually repaired my environment. I am not sure whether you want to fix that in this issue or separately, but the preflight/base-config bug above is enough to explain the self-lock.

Why this is worth fixing separately

There are already several issues around missing bundled runtime deps after packaged installs/upgrades. This one is narrower and should be actionable:

  • the scanner can already detect the missing dependency
  • the repair path already exists
  • doctor is just dropping the enabled-plugin config before it reaches that repair path

Related

Possibly related to:

  • #69897
  • #69923
  • #70027
  • #63309

but this report is specifically about the doctor --fix preflight/config handoff causing the repair path to skip an explicitly enabled plugin that is missing runtime deps.

extent analysis

TL;DR

The most likely fix is to modify the doctor-config-preflight.ts file to fall back to readSourceConfigBestEffort() when readConfigFileSnapshot() returns an invalid snapshot, ensuring that the correct config is used for bundled runtime dependency repair.

Guidance

  1. Verify the issue: Confirm that the problem occurs when a bundled plugin runtime dependency is missing and doctor --fix is run.
  2. Apply the suggested patch: Modify the doctor-config-preflight.ts file as shown in the suggested diff to use readSourceConfigBestEffort() when readConfigFileSnapshot() returns an invalid snapshot.
  3. Test the fix: Run openclaw doctor --fix after applying the patch to ensure that the missing dependency is installed and the config is validated correctly.
  4. Check for design inconsistencies: Investigate the secondary observation about the design inconsistency in installing bundled dependencies into each plugin runtime dir versus the package root.

Example

The suggested diff provides a concrete example of the code change needed to fix the issue:

diff --git a/src/commands/doctor-config-preflight.ts b/src/commands/doctor-config-preflight.ts
index ???????..??????? 100644
--- a/src/commands/doctor-config-preflight.ts
+++ b/src/commands/doctor-config-preflight.ts
@@
-import { readConfigFileSnapshot } from "../config/io";
+import { readConfigFileSnapshot, readSourceConfigBestEffort } from "../config/io";
@@
   const snapshot = await readConfigFileSnapshot();
   const invalidConfigNote = options.invalidConfigNote ?? "Config invalid; doctor will run with best-effort config.";
   if (invalidConfigNote && snapshot.exists && !snapshot.valid && snapshot.legacyIssues.length === 0) {
     note(invalidConfigNote, "Config");
     noteIncludeConfinementWarning(snapshot);
   }
   const warnings = snapshot.warnings ?? [];
   if (warnings.length > 0) note(formatConfigIssueLines(warnings, "-").join("\n"), "Config warnings");
+  const baseConfig = snapshot.valid
+    ? (snapshot.sourceConfig ?? snapshot.config ?? {})
+    : await readSourceConfigBestEffort();
   return {
     snapshot,
-    baseConfig: snapshot.sourceConfig ?? snapshot.config ?? {}
+    baseConfig
   };
 }

Notes

The fix assumes that the readSourceConfigBestEffort() function is correctly implemented and returns the expected config. Additionally, the design inconsistency in installing bundled dependencies may need to be addressed separately.

Recommendation

Apply the workaround by modifying the doctor-config-preflight.ts file as suggested, as this should fix the specific issue with doctor --fix skipping the repair path for an explicitly enabled plugin that is missing runtime dependencies.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix doctor --fix cannot recover missing bundled runtime deps when config preflight collapses invalid snapshot to {} [2 comments, 2 participants]