fix(plugins): canonicalize packageRoot before hashing runtime-deps stage key

This commit is contained in:
openperf
2026-04-30 19:18:19 +08:00
parent bd20f8e07e
commit a726001447
2 changed files with 38 additions and 1 deletions

View File

@@ -53,7 +53,9 @@ function isPackagedBundledPluginRoot(pluginRoot: string): boolean {
}
function createPathHash(value: string): string {
return createHash("sha256").update(path.resolve(value)).digest("hex").slice(0, 12);
// Hash the OS-canonical (realpath) form so symlinked / junctioned
// packageRoots converge on a single staging directory across call sites.
return createHash("sha256").update(realpathOrResolve(value)).digest("hex").slice(0, 12);
}
function sanitizePathSegment(value: string): string {

View File

@@ -2645,6 +2645,41 @@ describe("ensureBundledPluginRuntimeDeps", () => {
expect(path.basename(resolved).startsWith("openclaw-unknown-")).toBe(false);
});
const itSupportsPackageRootSymlinks = process.platform === "win32" ? it.skip : it;
itSupportsPackageRootSymlinks(
"stages bundled runtime deps to the same root for symlinked packageRoot views (issue #74963)",
() => {
const realParent = makeTempDir();
const stageDir = makeTempDir();
const realPackageRoot = path.join(realParent, "openclaw-real");
fs.mkdirSync(realPackageRoot, { recursive: true });
fs.writeFileSync(
path.join(realPackageRoot, "package.json"),
JSON.stringify({ name: "openclaw", version: "2026.4.27" }),
);
const realPluginRoot = path.join(realPackageRoot, "dist", "extensions", "discord");
fs.mkdirSync(realPluginRoot, { recursive: true });
fs.writeFileSync(
path.join(realPluginRoot, "package.json"),
JSON.stringify({ dependencies: {} }),
);
const linkedPackageRoot = path.join(realParent, "openclaw-linked");
fs.symlinkSync(realPackageRoot, linkedPackageRoot, "dir");
const linkedPluginRoot = path.join(linkedPackageRoot, "dist", "extensions", "discord");
const env = { OPENCLAW_PLUGIN_STAGE_DIR: stageDir };
const installRootViaReal = resolveBundledRuntimeDependencyInstallRoot(realPluginRoot, {
env,
});
const installRootViaLink = resolveBundledRuntimeDependencyInstallRoot(linkedPluginRoot, {
env,
});
expect(installRootViaLink).toBe(installRootViaReal);
expect(path.basename(installRootViaReal)).toMatch(/^openclaw-2026\.4\.27-[0-9a-f]{12}$/);
},
);
it("prunes stale unknown external runtime roots while keeping newest and locked roots", () => {
const stageDir = makeTempDir();
const nowMs = Date.parse("2026-04-29T08:00:00.000Z");