Skip to content

Bug: collectAlreadyStagedBundledRuntimeDepSpecs omits auto-loaded plugins, causing their deps to be pruned #75109

@jared-rebel

Description

@jared-rebel

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:

  1. memory-core's ensureBundledPluginRuntimeDeps writes manifest including chokidar@^5.0.0.
  2. The next plugin's ensureBundledPluginRuntimeDeps runs. collectAlreadyStagedBundledRuntimeDepSpecs scans with configmemory-core excluded → chokidar not in alreadyStagedSpecs.
  3. retainSpecIfActive("chokidar@^5.0.0") returns false (not in retainedAllowedSpecs).
  4. pruneRetainedRuntimeDepsManifestSpecs sees chokidar in previousSpecs but not in nextSpecsdeletes chokidar from node_modules.
  5. Next openclaw doctor run reports chokidar@^5.0.0 (used by memory-core) as missing and reinstalls it.
  6. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions