Skip to content

Commit 7f58e89

Browse files
committed
fix(plugins): prune old runtime deps package roots
1 parent d3bb5ce commit 7f58e89

4 files changed

Lines changed: 70 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai
1919
### Fixes
2020

2121
- Discord/voice: run voice-channel turns under a voice-output policy that hides the agent `tts` tool and asks for spoken reply text, so `/vc join` sessions synthesize and play agent replies instead of ending with `NO_REPLY`. Fixes #61536. Thanks @aounakram.
22+
- Plugins/runtime-deps: prune inactive same-package versioned runtime-deps roots after bundled dependency repair, so upgrades do not leave old `openclaw-<version>-<hash>` package caches behind after doctor runs. Thanks @vincentkoc.
2223
- Plugins/runtime-deps: prune legacy version-scoped plugin runtime-deps roots during bundled dependency repair and cover the path in Package Acceptance's upgrade-survivor matrix, so upgrades from 2026.4.x no longer leave stale per-plugin runtime trees after doctor runs. Thanks @vincentkoc.
2324
- Plugins/runtime-deps: keep Gateway startup plugin imports and runtime plugin fallback loads verify-only after startup/config repair planning, so packaged installs no longer spawn package-manager repair from hot paths after readiness. Refs #75283 and #75069. Thanks @brokemac79 and @xiaohuaxi.
2425
- Voice Call/realtime: add default-off fast memory/session context for `openclaw_agent_consult`, giving live calls a bounded answer-or-miss path before the full agent consult. Fixes #71849. Thanks @amzzzzzzz.

src/plugins/bundled-runtime-deps-roots.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,57 @@ export function listSiblingExternalBundledRuntimeDepsRoots(params: {
269269
.map((entry) => entry.root);
270270
}
271271

272+
export function pruneSiblingExternalBundledRuntimeDepsRoots(params: {
273+
installRoot: string;
274+
nowMs?: number;
275+
warn?: (message: string) => void;
276+
}): { scanned: number; removed: number; skippedLocked: number } {
277+
const installRoot = path.resolve(params.installRoot);
278+
const installRootHash = readPackageKeyPathHash(path.basename(installRoot));
279+
if (!installRootHash) {
280+
return { scanned: 0, removed: 0, skippedLocked: 0 };
281+
}
282+
const parentDir = path.dirname(installRoot);
283+
const nowMs = params.nowMs ?? Date.now();
284+
let entries: fs.Dirent[];
285+
try {
286+
entries = fs.readdirSync(parentDir, { withFileTypes: true });
287+
} catch {
288+
return { scanned: 0, removed: 0, skippedLocked: 0 };
289+
}
290+
291+
let scanned = 0;
292+
let removed = 0;
293+
let skippedLocked = 0;
294+
for (const entry of entries) {
295+
if (
296+
!entry.isDirectory() ||
297+
!entry.name.startsWith("openclaw-") ||
298+
readPackageKeyPathHash(entry.name) !== installRootHash
299+
) {
300+
continue;
301+
}
302+
const root = path.join(parentDir, entry.name);
303+
if (path.resolve(root) === installRoot) {
304+
continue;
305+
}
306+
scanned += 1;
307+
const lockDir = path.join(root, BUNDLED_RUNTIME_DEPS_LOCK_DIR);
308+
if (fs.existsSync(lockDir) && !removeRuntimeDepsLockIfStale(lockDir, nowMs)) {
309+
skippedLocked += 1;
310+
continue;
311+
}
312+
try {
313+
fs.rmSync(root, { recursive: true, force: true });
314+
removed += 1;
315+
} catch (error) {
316+
params.warn?.(`failed to remove sibling bundled runtime deps root ${root}: ${String(error)}`);
317+
}
318+
}
319+
320+
return { scanned, removed, skippedLocked };
321+
}
322+
272323
function readPackageKeyPathHash(packageKey: string): string | null {
273324
return PACKAGE_KEY_PATH_HASH_RE.exec(packageKey)?.[1] ?? null;
274325
}

src/plugins/bundled-runtime-deps.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1860,6 +1860,7 @@ describe("createBundledRuntimeDepsPackagePlan config policy", () => {
18601860
]);
18611861
expect(fs.lstatSync(path.join(installRoot, "node_modules")).isSymbolicLink()).toBe(true);
18621862
expect(isRuntimeDepsPlanMaterialized(installRoot, ["[email protected]"])).toBe(true);
1863+
expect(fs.existsSync(previousRoot)).toBe(true);
18631864
expect(JSON.parse(fs.readFileSync(path.join(installRoot, "package.json"), "utf8"))).toEqual({
18641865
name: "openclaw-runtime-deps-install",
18651866
private: true,
@@ -1904,6 +1905,7 @@ describe("createBundledRuntimeDepsPackagePlan config policy", () => {
19041905
},
19051906
]);
19061907
expect(fs.lstatSync(path.join(installRoot, "node_modules")).isSymbolicLink()).toBe(false);
1908+
expect(fs.existsSync(previousRoot)).toBe(true);
19071909
});
19081910

19091911
it("does not create a reuse symlink when an earlier configured layer already satisfies the plan", async () => {
@@ -1974,6 +1976,7 @@ describe("createBundledRuntimeDepsPackagePlan config policy", () => {
19741976
},
19751977
]);
19761978
expect(fs.lstatSync(path.join(installRoot, "node_modules")).isSymbolicLink()).toBe(false);
1979+
expect(fs.existsSync(previousRoot)).toBe(false);
19771980
});
19781981

19791982
it("does not reuse a compatible external runtime deps root from a different package key", async () => {

src/plugins/bundled-runtime-deps.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
import {
2626
isSourceCheckoutRoot,
2727
listSiblingExternalBundledRuntimeDepsRoots,
28+
pruneSiblingExternalBundledRuntimeDepsRoots,
2829
pruneUnknownBundledRuntimeDepsRoots,
2930
resolveBundledRuntimeDependencyInstallRootPlan,
3031
resolveBundledRuntimeDependencyPackageInstallRootPlan,
@@ -404,6 +405,10 @@ export async function repairBundledRuntimeDepsPackagePlanAsync(params: {
404405
});
405406
const plan = createBundledRuntimeDepsPackagePlan(params);
406407
if (plan.missingSpecs.length === 0) {
408+
pruneSiblingExternalBundledRuntimeDepsRoots({
409+
installRoot: plan.installRootPlan.installRoot,
410+
...(params.warn ? { warn: params.warn } : {}),
411+
});
407412
return { plan, repairedSpecs: [] };
408413
}
409414
const reuseResult = withBundledRuntimeDepsInstallRootLock(plan.installRootPlan.installRoot, () =>
@@ -416,6 +421,12 @@ export async function repairBundledRuntimeDepsPackagePlanAsync(params: {
416421
);
417422
if (reuseResult) {
418423
const refreshedPlan = createBundledRuntimeDepsPackagePlan(params);
424+
if (reuseResult.status === "materialized") {
425+
pruneSiblingExternalBundledRuntimeDepsRoots({
426+
installRoot: refreshedPlan.installRootPlan.installRoot,
427+
...(params.warn ? { warn: params.warn } : {}),
428+
});
429+
}
419430
return {
420431
plan: refreshedPlan,
421432
repairedSpecs: [],
@@ -442,6 +453,10 @@ export async function repairBundledRuntimeDepsPackagePlanAsync(params: {
442453
...(params.onProgress ? { onProgress: params.onProgress } : {}),
443454
...(params.warn ? { warn: params.warn } : {}),
444455
});
456+
pruneSiblingExternalBundledRuntimeDepsRoots({
457+
installRoot: plan.installRootPlan.installRoot,
458+
...(params.warn ? { warn: params.warn } : {}),
459+
});
445460
return { plan, repairedSpecs: result.installSpecs };
446461
}
447462

0 commit comments

Comments
 (0)