mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:30:43 +00:00
fix(bundled-runtime-deps): collapse nested cache pluginRoot to enclosing key
When a bundled plugin (e.g. plugin-sdk loaded transitively) is resolved via a pluginRoot already inside the existing plugin-runtime-deps cache, its path does not match the `dist/extensions/<plugin>` shape, so resolveBundledPluginPackageRoot() returns null and the caller falls back to the raw pluginRoot. resolveExistingExternalBundledRuntimeDepsRoots() then rejected the path because the relative segment crossed a directory separator, causing the resolver to mint a fresh `openclaw-unknown-<pathhash>` cache beside the real versioned one. The two caches raced replaceNodeModulesDir() and triggered ENOTEMPTY crash loops. Treat any descendant of `<base>/openclaw-*` as belonging to that cache key so nested resolutions return the existing versioned root instead of creating a self-referential zombie cache. Fixes #72956
This commit is contained in:
@@ -1696,6 +1696,42 @@ describe("ensureBundledPluginRuntimeDeps", () => {
|
||||
).toEqual({ installedSpecs: [], retainSpecs: [] });
|
||||
});
|
||||
|
||||
it("resolves nested cache pluginRoot to enclosing versioned cache (regression for #72956)", () => {
|
||||
const packageRoot = makeTempDir();
|
||||
const stageDir = makeTempDir();
|
||||
fs.writeFileSync(
|
||||
path.join(packageRoot, "package.json"),
|
||||
JSON.stringify({ name: "openclaw", version: "2026.4.25" }),
|
||||
);
|
||||
const pluginRoot = path.join(packageRoot, "dist", "extensions", "telegram");
|
||||
fs.mkdirSync(pluginRoot, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(pluginRoot, "package.json"),
|
||||
JSON.stringify({ dependencies: { grammy: "^1.42.0" } }),
|
||||
);
|
||||
const env = { OPENCLAW_PLUGIN_STAGE_DIR: stageDir };
|
||||
const installRoot = resolveBundledRuntimeDependencyInstallRoot(pluginRoot, { env });
|
||||
|
||||
// Simulate a deeply-nested pluginRoot inside the existing cache directory
|
||||
// (e.g. plugin-sdk loaded as a transitive dep). The path no longer matches
|
||||
// `<root>/dist/extensions/<plugin>`, so resolveBundledPluginPackageRoot()
|
||||
// returns null and the caller previously fell back to the raw pluginRoot,
|
||||
// generating a self-referential `openclaw-unknown-*` cache directory.
|
||||
const nestedPluginRoot = path.join(
|
||||
installRoot,
|
||||
"dist",
|
||||
"extensions",
|
||||
"node_modules",
|
||||
"openclaw",
|
||||
"plugin-sdk",
|
||||
);
|
||||
fs.mkdirSync(nestedPluginRoot, { recursive: true });
|
||||
|
||||
const resolved = resolveBundledRuntimeDependencyInstallRoot(nestedPluginRoot, { env });
|
||||
expect(resolved).toBe(installRoot);
|
||||
expect(path.basename(resolved).startsWith("openclaw-unknown-")).toBe(false);
|
||||
});
|
||||
|
||||
it("links source-checkout runtime deps from the cache instead of copying them", () => {
|
||||
const packageRoot = makeTempDir();
|
||||
fs.writeFileSync(
|
||||
|
||||
@@ -977,18 +977,20 @@ function resolveExistingExternalBundledRuntimeDepsRoots(params: {
|
||||
const externalBaseDirs = resolveBundledRuntimeDepsExternalBaseDirs(params.env);
|
||||
for (const externalBaseDir of externalBaseDirs) {
|
||||
const relative = path.relative(path.resolve(externalBaseDir), packageRoot);
|
||||
if (
|
||||
relative === "" ||
|
||||
relative.startsWith("..") ||
|
||||
path.isAbsolute(relative) ||
|
||||
relative.includes(path.sep)
|
||||
) {
|
||||
if (relative === "" || relative.startsWith("..") || path.isAbsolute(relative)) {
|
||||
continue;
|
||||
}
|
||||
const packageKey = path.basename(packageRoot);
|
||||
return packageKey.startsWith("openclaw-")
|
||||
? externalBaseDirs.map((baseDir) => path.join(baseDir, packageKey))
|
||||
: null;
|
||||
// Accept both `<base>/<key>` and any descendant such as
|
||||
// `<base>/<key>/dist/extensions/node_modules/openclaw/plugin-sdk`.
|
||||
// Without this, when a bundled package re-enters resolution via a nested
|
||||
// `pluginRoot` (e.g. plugin-sdk loaded as a dependency), the caller falls
|
||||
// back to `params.pluginRoot`, which lacks a `package.json`, producing a
|
||||
// self-referential `openclaw-unknown-*` cache directory (#72956).
|
||||
const packageKey = relative.split(path.sep)[0];
|
||||
if (!packageKey || !packageKey.startsWith("openclaw-")) {
|
||||
continue;
|
||||
}
|
||||
return externalBaseDirs.map((baseDir) => path.join(baseDir, packageKey));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user