From 93e27de58fb2f00892763b8cdd72d71449e3a658 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Fri, 2 Oct 2026 08:35:42 -0700 Subject: [PATCH] fix: macOS release upgrade checks fail during updater recovery (#163580) * fix(release): canonicalize macOS upgrade temp paths * test(release): canonicalize macOS temp fixture --- scripts/lib/cross-os-release-checks/lanes.ts | 10 ++-- ...oss-os-release-checks-upgrade-lane.test.ts | 48 ++++++++++++++++++- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/scripts/lib/cross-os-release-checks/lanes.ts b/scripts/lib/cross-os-release-checks/lanes.ts index efe2abaad8e2..b3c69e22e001 100644 --- a/scripts/lib/cross-os-release-checks/lanes.ts +++ b/scripts/lib/cross-os-release-checks/lanes.ts @@ -921,7 +921,9 @@ export async function runDevUpdateSuite( } function createLaneState(name: string): LaneState { - const rootDir = mkdtempSync(join(tmpdir(), `openclaw-${name}-`)); + const createdRootDir = mkdtempSync(join(tmpdir(), `openclaw-${name}-`)); + const rootDir = + process.platform === "darwin" ? realpathSync.native(createdRootDir) : createdRootDir; const prefixDir = join(rootDir, "prefix"); const homeDir = join(rootDir, "home"); const stateDir = join(homeDir, ".openclaw"); @@ -971,9 +973,9 @@ function buildLaneEnv( function inheritLaneEnv(): NodeJS.ProcessEnv { const env = { ...process.env }; - if (process.platform === "win32") { - // Published updaters cannot be patched: use long paths for their handoff - // receipts while keeping the runner's existing physical temp directories. + if (process.platform === "win32" || process.platform === "darwin") { + // Published updaters cannot be patched: use canonical paths for their + // handoff receipts while keeping the runner's physical temp directories. for (const key of Object.keys(env)) { const value = env[key]; if (["TEMP", "TMP", "TMPDIR"].includes(key.toUpperCase()) && value) { diff --git a/test/scripts/openclaw-cross-os-release-checks-upgrade-lane.test.ts b/test/scripts/openclaw-cross-os-release-checks-upgrade-lane.test.ts index c18f033189ba..97f760331f59 100644 --- a/test/scripts/openclaw-cross-os-release-checks-upgrade-lane.test.ts +++ b/test/scripts/openclaw-cross-os-release-checks-upgrade-lane.test.ts @@ -1,5 +1,5 @@ import { ChildProcess, spawn, spawnSync } from "node:child_process"; -import { mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs"; +import { mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; import { createServer } from "node:net"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -246,6 +246,52 @@ describe("cross-OS manual gateway lane evidence", () => { }, ); + it.runIf(process.platform !== "win32")( + "canonicalizes macOS temp paths before invoking the published updater", + async () => { + arrangeSuccessfulLane(); + const createdPhysicalTemp = join(logsDir, "physical-temp"); + const aliasedTemp = join(logsDir, "aliased-temp"); + mkdirSync(createdPhysicalTemp); + const physicalTemp = realpathSync.native(createdPhysicalTemp); + symlinkSync(physicalTemp, aliasedTemp, process.platform === "win32" ? "junction" : "dir"); + vi.mocked(tmpdir).mockReturnValue(aliasedTemp); + vi.spyOn(process, "platform", "get").mockReturnValue("darwin"); + for (const key of ["TEMP", "TMP", "TMPDIR", "Temp"]) { + vi.stubEnv(key, aliasedTemp); + } + + const result = await runUpgradeLane(upgradeParams()); + + expect(result).toMatchObject({ status: "pass" }); + const baselineInstall = mocks.installPackageSpec.mock.calls[0]?.[0]; + expect(baselineInstall?.lane.rootDir).toMatch( + new RegExp(`^${physicalTemp.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`), + ); + expect(baselineInstall?.env).toMatchObject({ + TEMP: physicalTemp, + TMP: physicalTemp, + TMPDIR: physicalTemp, + Temp: physicalTemp, + }); + expect(mocks.runOpenClaw).toHaveBeenCalledWith( + expect.objectContaining({ + args: expect.arrayContaining(["update", "--json"]), + env: expect.objectContaining({ + TEMP: physicalTemp, + TMP: physicalTemp, + TMPDIR: physicalTemp, + Temp: physicalTemp, + NPM_CONFIG_PREFIX: expect.stringMatching( + new RegExp(`^${physicalTemp.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`), + ), + }), + }), + ); + expect(process.env.TEMP).toBe(aliasedTemp); + }, + ); + describe.each([ ["fresh", runFreshLane], ["upgrade", runUpgradeLane],