From d46f64199aede4b4c4f2945fa7e239faefa95067 Mon Sep 17 00:00:00 2001 From: Shakker Date: Mon, 30 Mar 2026 23:18:32 +0100 Subject: [PATCH] fix: retry bundled runtime dependency staging --- scripts/stage-bundled-plugin-runtime-deps.mjs | 32 ++++++++-- .../stage-bundled-plugin-runtime-deps.test.ts | 59 +++++++++++++++++++ 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/scripts/stage-bundled-plugin-runtime-deps.mjs b/scripts/stage-bundled-plugin-runtime-deps.mjs index 27d728bf298..ca34964bdb9 100644 --- a/scripts/stage-bundled-plugin-runtime-deps.mjs +++ b/scripts/stage-bundled-plugin-runtime-deps.mjs @@ -266,10 +266,28 @@ function installPluginRuntimeDeps(params) { } } +function installPluginRuntimeDepsWithRetries(params) { + const { attempts = 3 } = params; + let lastError; + for (let attempt = 1; attempt <= attempts; attempt += 1) { + try { + params.install({ ...params.installParams, attempt }); + return; + } catch (error) { + lastError = error; + if (attempt === attempts) { + break; + } + } + } + throw lastError; +} + export function stageBundledPluginRuntimeDeps(params = {}) { const repoRoot = params.cwd ?? params.repoRoot ?? process.cwd(); const installPluginRuntimeDepsImpl = params.installPluginRuntimeDepsImpl ?? installPluginRuntimeDeps; + const installAttempts = params.installAttempts ?? 3; for (const pluginDir of listBundledPluginRuntimeDirs(repoRoot)) { const pluginId = path.basename(pluginDir); const packageJson = sanitizeBundledManifestForRuntimeInstall(pluginDir); @@ -285,11 +303,15 @@ export function stageBundledPluginRuntimeDeps(params = {}) { if (fs.existsSync(nodeModulesDir) && stamp?.fingerprint === fingerprint) { continue; } - installPluginRuntimeDepsImpl({ - fingerprint, - packageJson, - pluginDir, - pluginId, + installPluginRuntimeDepsWithRetries({ + attempts: installAttempts, + install: installPluginRuntimeDepsImpl, + installParams: { + fingerprint, + packageJson, + pluginDir, + pluginId, + }, }); } } diff --git a/test/scripts/stage-bundled-plugin-runtime-deps.test.ts b/test/scripts/stage-bundled-plugin-runtime-deps.test.ts index d6f7d2acb8b..a4d4164c07c 100644 --- a/test/scripts/stage-bundled-plugin-runtime-deps.test.ts +++ b/test/scripts/stage-bundled-plugin-runtime-deps.test.ts @@ -228,4 +228,63 @@ describe("stageBundledPluginRuntimeDeps", () => { expect(installCount).toBe(2); expect(fs.readFileSync(path.join(pluginDir, "node_modules", "marker.txt"), "utf8")).toBe("2\n"); }); + + it("retries transient runtime dependency staging failures before surfacing an error", () => { + const { pluginDir, repoRoot } = createBundledPluginFixture({ + packageJson: { + name: "@openclaw/fixture-plugin", + version: "1.0.0", + dependencies: { "left-pad": "1.3.0" }, + openclaw: { bundle: { stageRuntimeDependencies: true } }, + }, + }); + + let installCount = 0; + stageBundledPluginRuntimeDeps({ + cwd: repoRoot, + installPluginRuntimeDepsImpl: ({ fingerprint }: { fingerprint: string }) => { + installCount += 1; + if (installCount < 3) { + throw new Error(`attempt ${installCount} failed`); + } + const nodeModulesDir = path.join(pluginDir, "node_modules"); + fs.mkdirSync(nodeModulesDir, { recursive: true }); + fs.writeFileSync(path.join(nodeModulesDir, "marker.txt"), "ok\n", "utf8"); + fs.writeFileSync( + path.join(pluginDir, ".openclaw-runtime-deps-stamp.json"), + `${JSON.stringify({ fingerprint }, null, 2)}\n`, + "utf8", + ); + }, + }); + + expect(installCount).toBe(3); + expect(fs.readFileSync(path.join(pluginDir, "node_modules", "marker.txt"), "utf8")).toBe( + "ok\n", + ); + }); + + it("surfaces the last staging error after exhausting retries", () => { + const { repoRoot } = createBundledPluginFixture({ + packageJson: { + name: "@openclaw/fixture-plugin", + version: "1.0.0", + dependencies: { "left-pad": "1.3.0" }, + openclaw: { bundle: { stageRuntimeDependencies: true } }, + }, + }); + + let installCount = 0; + expect(() => + stageBundledPluginRuntimeDeps({ + cwd: repoRoot, + installAttempts: 2, + installPluginRuntimeDepsImpl: () => { + installCount += 1; + throw new Error(`attempt ${installCount} failed`); + }, + }), + ).toThrow("attempt 2 failed"); + expect(installCount).toBe(2); + }); });