diff --git a/src/plugins/loader.test.ts b/src/plugins/loader.test.ts index 689d4c67df3..ee455af9202 100644 --- a/src/plugins/loader.test.ts +++ b/src/plugins/loader.test.ts @@ -560,6 +560,15 @@ module.exports = { id: "skipped", register() { throw new Error("skipped plugin s expect(getGlobalHookRunner()).toBeNull(); }); + it("throws when activate:false is used without cache:false", () => { + expect(() => loadOpenClawPlugins({ activate: false })).toThrow( + "activate:false requires cache:false", + ); + expect(() => loadOpenClawPlugins({ activate: false, cache: true })).toThrow( + "activate:false requires cache:false", + ); + }); + it("re-initializes global hook runner when serving registry from cache", () => { process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins"; const plugin = writePlugin({ diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts index cfd3af6a8e8..f25009b591f 100644 --- a/src/plugins/loader.ts +++ b/src/plugins/loader.ts @@ -641,6 +641,13 @@ function activatePluginRegistry(registry: PluginRegistry, cacheKey: string): voi } export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegistry { + // Snapshot (non-activating) loads must disable the cache to avoid storing a registry + // whose commands were never globally registered. + if (options.activate === false && options.cache !== false) { + throw new Error( + "loadOpenClawPlugins: activate:false requires cache:false to prevent command registry divergence", + ); + } const env = options.env ?? process.env; // Test env: default-disable plugins unless explicitly configured. // This keeps unit/gateway suites fast and avoids loading heavyweight plugin deps by accident.