fix: macOS release upgrade checks fail during updater recovery (#163580)

* fix(release): canonicalize macOS upgrade temp paths

* test(release): canonicalize macOS temp fixture
This commit is contained in:
Dallin Romney 2026-10-02 08:35:42 -07:00 • committed by GitHub
parent b7eac4df09
commit 93e27de58f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 53 additions and 5 deletions

View file

@ -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) {

View file

@ -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],