From 7482754aca89a8760fa570a9ec981fa3cccc35a7 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 4 May 2026 01:49:14 -0700 Subject: [PATCH] fix(plugins): avoid duplicate native fallback loads --- CHANGELOG.md | 1 + .../plugin-module-loader-cache.test.ts | 43 ++++++++++++++++++- src/plugins/plugin-module-loader-cache.ts | 1 - 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f49ef25d00..613ce4bc301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Plugins/loader: do not retry native-loaded JavaScript plugin modules through the source transformer after native evaluation has already reached a missing dependency, avoiding duplicate top-level side effects. Thanks @vincentkoc. - Plugins/packages: reject blank `openclaw.runtimeExtensions` entries instead of silently ignoring them and falling back to inferred TypeScript runtime entries. Thanks @vincentkoc. - Doctor/plugins: remove stale managed npm plugin shadow entries from the managed package lock as well as `package.json` and `node_modules`, so future npm operations do not keep referencing repaired bundled-plugin shadows. Thanks @vincentkoc. - Plugins/runtime state: keep the key being registered when namespace eviction runs in the same millisecond as existing entries, so `register` and `registerIfAbsent` do not report success while evicting their own fresh value. Thanks @vincentkoc. diff --git a/src/plugins/plugin-module-loader-cache.test.ts b/src/plugins/plugin-module-loader-cache.test.ts index 8004266bf3e..2fd80e1a34a 100644 --- a/src/plugins/plugin-module-loader-cache.test.ts +++ b/src/plugins/plugin-module-loader-cache.test.ts @@ -406,7 +406,6 @@ describe("getCachedPluginModuleLoader", () => { // allowWindows must be passed so the native fast path works on Windows too. expect(nativeStub).toHaveBeenCalledWith("/repo/dist/extensions/demo/api.js", { allowWindows: true, - fallbackOnMissingDependency: true, }); expect(getPluginModuleLoaderStats()).toMatchObject({ calls: 1, @@ -417,6 +416,48 @@ describe("getCachedPluginModuleLoader", () => { }); }); + it("does not source-transform fallback after native loading reaches a missing dependency", async () => { + const fromSourceTransformer = vi.fn(); + const createJiti = vi.fn(() => fromSourceTransformer); + vi.doMock("jiti", () => ({ createJiti })); + const missingDependency = Object.assign(new Error("Cannot find module 'missing-dep'"), { + code: "MODULE_NOT_FOUND", + }); + const nativeStub = vi.fn(() => { + throw missingDependency; + }); + vi.doMock("./native-module-require.js", () => ({ + isJavaScriptModulePath: () => true, + tryNativeRequireJavaScriptModule: nativeStub, + })); + const { getCachedPluginModuleLoader, getPluginModuleLoaderStats } = await importFreshModule< + typeof import("./plugin-module-loader-cache.js") + >(import.meta.url, "./plugin-module-loader-cache.js?scope=native-missing-dependency"); + + const cache = new Map(); + const loader = getCachedPluginModuleLoader({ + cache, + modulePath: "/repo/dist/extensions/demo/api.js", + importerUrl: "file:///repo/src/plugins/public-surface-loader.ts", + loaderFilename: "file:///repo/src/plugins/public-surface-loader.ts", + createLoader: asPluginModuleLoaderFactory(createJiti), + }); + + expect(() => loader("/repo/dist/extensions/demo/api.js")).toThrow("missing-dep"); + expect(createJiti).not.toHaveBeenCalled(); + expect(fromSourceTransformer).not.toHaveBeenCalled(); + expect(nativeStub).toHaveBeenCalledWith("/repo/dist/extensions/demo/api.js", { + allowWindows: true, + }); + expect(getPluginModuleLoaderStats()).toMatchObject({ + calls: 1, + nativeHits: 0, + nativeMisses: 0, + sourceTransformFallbacks: 0, + sourceTransformForced: 0, + }); + }); + it("falls back to source transform when the native-require helper declines", async () => { const fromSourceTransformer = vi.fn(() => ({ fromSourceTransform: true })); const createJiti = vi.fn(() => fromSourceTransformer); diff --git a/src/plugins/plugin-module-loader-cache.ts b/src/plugins/plugin-module-loader-cache.ts index d7bbc38bdcf..f73850b2ba0 100644 --- a/src/plugins/plugin-module-loader-cache.ts +++ b/src/plugins/plugin-module-loader-cache.ts @@ -284,7 +284,6 @@ function createPluginModuleLoader(params: { } const native = tryNativeRequireJavaScriptModule(target, { allowWindows: true, - fallbackOnMissingDependency: true, }); if (native.ok) { pluginModuleLoaderStats.nativeHits += 1;