fix: add facade recursion regression coverage (#57508) (thanks @openperf)

This commit is contained in:
Ayaan Zaidi 2026-03-30 13:46:25 +05:30
parent 9a03fe8181
commit 0b632dde8c
2 changed files with 43 additions and 0 deletions

View File

@ -96,6 +96,7 @@ Docs: https://docs.openclaw.ai
- Agents/Anthropic failover: treat Anthropic `api_error` payloads with `An unexpected error occurred while processing the response` as transient so retry/fallback can engage instead of surfacing a terminal failure. (#57441) Thanks @zijiess and @vincentkoc.
- Agents/compaction: keep late compaction-retry rejections handled after the aggregate timeout path wins without swallowing real pre-timeout wait failures, so timed-out retries no longer surface an unhandled rejection on later unsubscribe. (#57451) Thanks @mpz4life and @vincentkoc.
- Matrix/delivery recovery: treat Synapse `User not in room` replay failures as permanent during startup recovery so poisoned queued messages move to `failed/` instead of crash-looping Matrix after restart. (#57426) thanks @dlardo.
- Plugins/facades: guard bundled plugin facade loads with a cache-first sentinel so circular re-entry stops crashing `xai`, `sglang`, and `vllm` during gateway plugin startup. (#57508) Thanks @openperf.
## 2026.3.28

View File

@ -31,6 +31,36 @@ function createThrowingPluginDir(prefix: string): string {
return rootDir;
}
function createCircularPluginDir(prefix: string): string {
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
tempDirs.push(rootDir);
fs.mkdirSync(path.join(rootDir, "demo"), { recursive: true });
fs.writeFileSync(
path.join(rootDir, "facade.mjs"),
[
`import { loadBundledPluginPublicSurfaceModuleSync } from ${JSON.stringify(
new URL("./facade-runtime.js", import.meta.url).href,
)};`,
`export const marker = loadBundledPluginPublicSurfaceModuleSync({ dirName: "demo", artifactBasename: "api.js" }).marker;`,
"",
].join("\n"),
"utf8",
);
fs.writeFileSync(
path.join(rootDir, "demo", "helper.js"),
['import { marker } from "../facade.mjs";', "export const circularMarker = marker;", ""].join(
"\n",
),
"utf8",
);
fs.writeFileSync(
path.join(rootDir, "demo", "api.js"),
['import "./helper.js";', 'export const marker = "circular-ok";', ""].join("\n"),
"utf8",
);
return rootDir;
}
afterEach(() => {
vi.restoreAllMocks();
if (originalBundledPluginsDir === undefined) {
@ -79,6 +109,18 @@ describe("plugin-sdk facade runtime", () => {
expect(first.marker).toBe("identity-check");
});
it("breaks circular facade re-entry during module evaluation", () => {
const dir = createCircularPluginDir("openclaw-facade-circular-");
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;
const loaded = loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
dirName: "demo",
artifactBasename: "api.js",
});
expect(loaded.marker).toBe("circular-ok");
});
it("clears the cache on load failure so retries re-execute", () => {
const dir = createThrowingPluginDir("openclaw-facade-throw-");
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;