Summary
collectAlreadyStagedBundledRuntimeDepSpecs calls collectBundledPluginRuntimeDeps({ extensionsDir, config }) with the user config. This causes auto-loaded bundled plugins that are not explicitly present in config.plugins.entries (e.g. memory-core) to be excluded from the dep scan. As a result their deps (e.g. chokidar@^5.0.0) never appear in alreadyStagedSpecs, which prevents them from being retained when other plugins run ensureBundledPluginRuntimeDeps.
Reproduction
Environment: OpenClaw 2026.4.27, Linux, packaged install (/usr/lib/node_modules/openclaw), memory-core not in config.plugins.entries (auto-loaded).
Trigger: Any plugin's ensureBundledPluginRuntimeDeps runs after memory-core's own run has written the manifest with chokidar@^5.0.0.
Observed behavior:
memory-core's ensureBundledPluginRuntimeDeps writes manifest including chokidar@^5.0.0.
- The next plugin's
ensureBundledPluginRuntimeDeps runs. collectAlreadyStagedBundledRuntimeDepSpecs scans with config → memory-core excluded → chokidar not in alreadyStagedSpecs.
retainSpecIfActive("chokidar@^5.0.0") returns false (not in retainedAllowedSpecs).
pruneRetainedRuntimeDepsManifestSpecs sees chokidar in previousSpecs but not in nextSpecs → deletes chokidar from node_modules.
- Next
openclaw doctor run reports chokidar@^5.0.0 (used by memory-core) as missing and reinstalls it.
- Cycle repeats on every gateway restart.
Verification: scanBundledPluginRuntimeDeps({ packageRoot, config }) (no pluginIds) returns 33 deps with chokidar: undefined. Same call with pluginIds: effectivePluginIds returns 19 deps with chokidar correctly present.
Root cause
collectAlreadyStagedBundledRuntimeDepSpecs (in bundled-runtime-root-D11Fl_T4.js):
// CURRENT (buggy)
const { deps, pluginIds } = collectBundledPluginRuntimeDeps({
extensionsDir,
config: params.config // excludes auto-loaded plugins not in config.plugins.entries
});
The purpose of this function is to find what's already physically installed in the install root — not what the current config activates. Passing config causes auto-loaded bundled plugins to be invisible here, breaking the retention chain for their deps.
Fix
Remove config from the collectBundledPluginRuntimeDeps call so all bundled plugins are scanned. The existing hasDependencySentinel([params.installRoot], dep) filter already ensures only physically-installed deps are returned, so scanning more candidate plugins is safe.
// FIXED
const { deps, pluginIds } = collectBundledPluginRuntimeDeps({
extensionsDir
// no config — scan all plugins; hasDependencySentinel filters to only installed ones
});
This was confirmed to stop the delete/reinstall cycle: with config omitted, chokidar appears in alreadyStagedSpecs, gets retained through other plugins' ensureBundledPluginRuntimeDeps runs, and is no longer pruned.
Impact
- File watching (config reload, memory updates) silently broken between gateway restarts and the next
doctor run.
openclaw doctor always reports chokidar@^5.0.0 (used by memory-core) as missing on installs where memory-core is auto-loaded.
- Unnecessary npm reinstall on every doctor run.
Summary
collectAlreadyStagedBundledRuntimeDepSpecscallscollectBundledPluginRuntimeDeps({ extensionsDir, config })with the user config. This causes auto-loaded bundled plugins that are not explicitly present inconfig.plugins.entries(e.g.memory-core) to be excluded from the dep scan. As a result their deps (e.g.chokidar@^5.0.0) never appear inalreadyStagedSpecs, which prevents them from being retained when other plugins runensureBundledPluginRuntimeDeps.Reproduction
Environment: OpenClaw 2026.4.27, Linux, packaged install (
/usr/lib/node_modules/openclaw),memory-corenot inconfig.plugins.entries(auto-loaded).Trigger: Any plugin's
ensureBundledPluginRuntimeDepsruns aftermemory-core's own run has written the manifest withchokidar@^5.0.0.Observed behavior:
memory-core'sensureBundledPluginRuntimeDepswrites manifest includingchokidar@^5.0.0.ensureBundledPluginRuntimeDepsruns.collectAlreadyStagedBundledRuntimeDepSpecsscans withconfig→memory-coreexcluded →chokidarnot inalreadyStagedSpecs.retainSpecIfActive("chokidar@^5.0.0")returnsfalse(not inretainedAllowedSpecs).pruneRetainedRuntimeDepsManifestSpecsseeschokidarinpreviousSpecsbut not innextSpecs→ deleteschokidarfromnode_modules.openclaw doctorrun reportschokidar@^5.0.0 (used by memory-core)as missing and reinstalls it.Verification:
scanBundledPluginRuntimeDeps({ packageRoot, config })(nopluginIds) returns 33 deps withchokidar: undefined. Same call withpluginIds: effectivePluginIdsreturns 19 deps with chokidar correctly present.Root cause
collectAlreadyStagedBundledRuntimeDepSpecs(inbundled-runtime-root-D11Fl_T4.js):The purpose of this function is to find what's already physically installed in the install root — not what the current config activates. Passing
configcauses auto-loaded bundled plugins to be invisible here, breaking the retention chain for their deps.Fix
Remove
configfrom thecollectBundledPluginRuntimeDepscall so all bundled plugins are scanned. The existinghasDependencySentinel([params.installRoot], dep)filter already ensures only physically-installed deps are returned, so scanning more candidate plugins is safe.This was confirmed to stop the delete/reinstall cycle: with config omitted,
chokidarappears inalreadyStagedSpecs, gets retained through other plugins'ensureBundledPluginRuntimeDepsruns, and is no longer pruned.Impact
doctorrun.openclaw doctoralways reportschokidar@^5.0.0 (used by memory-core)as missing on installs wherememory-coreis auto-loaded.