From bb5d7d0a3900b630f7dccb8953f591c0077ca1f3 Mon Sep 17 00:00:00 2001 From: cthomas Date: Fri, 27 Feb 2026 14:54:02 -0800 Subject: [PATCH] fix: make letta update resilient to npm extraction race conditions (#1201) --- package.json | 2 +- src/updater/auto-update.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index dbd18d9..a049af2 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "test:update-chain:manual": "bun run src/tests/update-chain-smoke.ts --mode manual", "test:update-chain:startup": "bun run src/tests/update-chain-smoke.ts --mode startup", "prepublishOnly": "bun run build", - "postinstall": "node scripts/postinstall-patches.js" + "postinstall": "node scripts/postinstall-patches.js || echo letta: vendor patches skipped" }, "lint-staged": { "*.{ts,tsx,js,jsx,json}": [ diff --git a/src/updater/auto-update.ts b/src/updater/auto-update.ts index 9b1654e..8d9e4f5 100644 --- a/src/updater/auto-update.ts +++ b/src/updater/auto-update.ts @@ -323,6 +323,32 @@ async function performUpdate(): Promise<{ } } + // npm race condition retry: covers TAR_ENTRY_ERROR (parallel extraction races), + // uv_cwd (npm CWD deleted during atomic package swap), and spawn sh ENOENT + // (sharp postinstall CWD missing). All are transient npm parallelism issues. + const isNpmRaceCondition = + pm === "npm" && + (errorMsg.includes("TAR_ENTRY_ERROR") || + errorMsg.includes("uv_cwd") || + (errorMsg.includes("spawn sh") && errorMsg.includes("ENOENT"))); + + if (isNpmRaceCondition) { + debugLog("npm race condition detected, cleaning up and retrying..."); + if (globalPath) { + await cleanupOrphanedDirs(globalPath); + } + try { + await execFileAsync(pm, installArgs, { timeout: 60000 }); + debugLog("Update succeeded after race condition retry"); + return { success: true }; + } catch (retryError) { + const retryMsg = + retryError instanceof Error ? retryError.message : String(retryError); + debugLog("Update failed after race condition retry:", retryMsg); + return { success: false, error: retryMsg }; + } + } + debugLog("Update failed:", error); return { success: false, error: errorMsg }; }