fix: tolerate runtime deps temp cleanup races

This commit is contained in:
Peter Steinberger
2026-04-23 07:01:27 +01:00
parent acb8fe986d
commit cc343febfb
2 changed files with 63 additions and 1 deletions

View File

@@ -261,6 +261,63 @@ describe("installBundledRuntimeDeps", () => {
);
});
it("does not fail an isolated runtime deps install when temp cleanup races", () => {
const installRoot = makeTempDir();
const installExecutionRoot = makeTempDir();
const realRmSync = fs.rmSync.bind(fs);
let blockedCleanup = false;
vi.spyOn(fs, "rmSync").mockImplementation((target, options) => {
if (
!blockedCleanup &&
path.basename(String(target)).startsWith(".openclaw-runtime-deps-copy-")
) {
blockedCleanup = true;
const error = new Error("Directory not empty") as NodeJS.ErrnoException;
error.code = "ENOTEMPTY";
throw error;
}
return realRmSync(target, options);
});
spawnSyncMock.mockImplementation((_command, _args, options) => {
const cwd = String(options?.cwd ?? "");
fs.mkdirSync(path.join(cwd, "node_modules", "tokenjuice"), { recursive: true });
fs.writeFileSync(
path.join(cwd, "node_modules", "tokenjuice", "package.json"),
JSON.stringify({ name: "tokenjuice", version: "0.6.1" }),
);
return {
pid: 123,
output: [],
stdout: "",
stderr: "",
signal: null,
status: 0,
};
});
expect(() =>
installBundledRuntimeDeps({
installRoot,
installExecutionRoot,
missingSpecs: ["tokenjuice@0.6.1"],
env: {},
}),
).not.toThrow();
expect(blockedCleanup).toBe(true);
expect(
JSON.parse(
fs.readFileSync(
path.join(installRoot, "node_modules", "tokenjuice", "package.json"),
"utf8",
),
),
).toEqual({
name: "tokenjuice",
version: "0.6.1",
});
});
it("rejects invalid install specs before spawning npm", () => {
expect(() =>
createBundledRuntimeDepsInstallArgs(["tokenjuice@https://evil.example/t.tgz"]),

View File

@@ -382,7 +382,12 @@ function replaceNodeModulesDir(targetDir: string, sourceDir: string): void {
fs.rmSync(targetDir, { recursive: true, force: true });
fs.renameSync(stagedDir, targetDir);
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
try {
fs.rmSync(tempDir, { recursive: true, force: true });
} catch {
// Stale temp dirs are swept at the next runtime-deps pass. Do not fail
// a node_modules replacement on a transient cleanup race.
}
}
}