diff --git a/scripts/e2e/parallels/npm-update-smoke.ts b/scripts/e2e/parallels/npm-update-smoke.ts index ca9f62d4b86e..850aa6d06d9b 100755 --- a/scripts/e2e/parallels/npm-update-smoke.ts +++ b/scripts/e2e/parallels/npm-update-smoke.ts @@ -61,6 +61,7 @@ interface NpmUpdateOptions { registryPackageTarballs: string[]; freshTargetSpec?: string; hostIp?: string; + macosSnapshotHint?: string; macosVm?: string; packageSpec: string; targetTarball?: string; @@ -390,6 +391,8 @@ Options: --platform Comma-separated platforms to run: all, macos, windows, linux. Default: all --macos-vm Explicit Parallels macOS VM name. + --macos-snapshot-hint + Snapshot name substring/fuzzy match passed to macOS fresh lanes. --provider --model Override the model used for agent-turn smoke checks. --host-ip Override Parallels host IP. @@ -409,6 +412,7 @@ export function parseArgs(argv: string[]): NpmUpdateOptions { registryPackageTarballs: [], freshTargetSpec: undefined, json: false, + macosSnapshotHint: undefined, macosVm: undefined, modelId: undefined, packageSpec: "", @@ -465,6 +469,10 @@ export function parseArgs(argv: string[]): NpmUpdateOptions { options.macosVm = ensureValue(args, i, arg); i++; break; + case "--macos-snapshot-hint": + options.macosSnapshotHint = ensureValue(args, i, arg); + i++; + break; case "--provider": options.provider = parseProvider(ensureValue(args, i, arg)); i++; @@ -691,7 +699,7 @@ export class NpmUpdateSmoke { private async runFreshBaselines(): Promise { const jobs: Job[] = []; if (this.options.platforms.has("macos")) { - jobs.push(this.spawnFresh("macOS", "macos", ["--vm", this.macosVm])); + jobs.push(this.spawnFresh("macOS", "macos", this.macosFreshArgs())); } if (this.options.platforms.has("windows")) { jobs.push(this.spawnFresh("Windows", "windows", [])); @@ -713,7 +721,7 @@ export class NpmUpdateSmoke { this.spawnFresh( "macOS", "macos", - ["--vm", this.macosVm], + this.macosFreshArgs(), {}, this.freshTargetSpec, "fresh-target", @@ -742,6 +750,16 @@ export class NpmUpdateSmoke { await this.finishFreshJobs("fresh-target", "fresh target", jobs, this.freshTargetStatus); } + private macosFreshArgs(): string[] { + return [ + "--vm", + this.macosVm, + ...(this.options.macosSnapshotHint + ? ["--snapshot-hint", this.options.macosSnapshotHint] + : []), + ]; + } + private async finishFreshJobs( phase: "fresh" | "fresh-target", failureLabel: string, diff --git a/scripts/release-candidate-checklist.d.mts b/scripts/release-candidate-checklist.d.mts index c16eba58ea35..fcee6894fd75 100644 --- a/scripts/release-candidate-checklist.d.mts +++ b/scripts/release-candidate-checklist.d.mts @@ -256,12 +256,14 @@ export function candidateParallelsArgs( dependencyTarballPaths?: unknown[], toolingRoot?: string, registryPackageTarballPaths?: unknown[], + macosSnapshotHint?: string, ): unknown[]; export function candidateParallelsShellCommand( tarballPath: unknown, timeoutBin: unknown, dependencyTarballPaths?: unknown[], registryPackageTarballPaths?: unknown[], + macosSnapshotHint?: string, ): string; declare function gitIsAncestor(ancestor: unknown, target: unknown): boolean; declare function loadCandidateShippedBaseline(ref: unknown): { diff --git a/scripts/release-candidate-checklist.mjs b/scripts/release-candidate-checklist.mjs index 4c359281b89c..ca72fe6d49b3 100644 --- a/scripts/release-candidate-checklist.mjs +++ b/scripts/release-candidate-checklist.mjs @@ -1361,6 +1361,7 @@ export function candidateParallelsArgs( dependencyTarballPaths = [], toolingRoot = TOOLING_ROOT, registryPackageTarballPaths = [], + macosSnapshotHint = "", ) { return [ "exec", @@ -1373,6 +1374,7 @@ export function candidateParallelsArgs( "--registry-package-tarball", registryPackage, ]), + ...(macosSnapshotHint ? ["--macos-snapshot-hint", macosSnapshotHint] : []), "--json", ]; } @@ -1382,6 +1384,7 @@ export function candidateParallelsShellCommand( timeoutBin, dependencyTarballPaths = [], registryPackageTarballPaths = [], + macosSnapshotHint = "", ) { // Login shells can replace the candidate's supported Node with ambient host Node. // Keep the invoking Node first so pnpm and npm use the validated runtime. @@ -1399,6 +1402,7 @@ export function candidateParallelsShellCommand( dependencyTarballPaths, TOOLING_ROOT, registryPackageTarballPaths, + macosSnapshotHint, ).map(shellQuote), ].join(" "); } @@ -1425,6 +1429,7 @@ async function runParallelsIfNeeded( timeoutBin, dependencyTarballPaths, registryPackageTarballPaths, + process.env.OPENCLAW_PARALLELS_MACOS_SNAPSHOT_HINT?.trim() ?? "", ); run("bash", ["-lc", command], { env: { diff --git a/test/scripts/parallels-npm-update-smoke.test.ts b/test/scripts/parallels-npm-update-smoke.test.ts index 116af0d348ee..706798101d0c 100644 --- a/test/scripts/parallels-npm-update-smoke.test.ts +++ b/test/scripts/parallels-npm-update-smoke.test.ts @@ -137,6 +137,15 @@ describe("parallels npm update smoke", () => { ).toThrow("--registry-package-tarball requires --target-tarball"); }); + it("passes an explicit macOS snapshot hint through fresh lanes", () => { + expect( + parseArgs(["--platform", "macos", "--macos-snapshot-hint", "macOS 26.5 Node 24"]), + ).toMatchObject({ + macosSnapshotHint: "macOS 26.5 Node 24", + platforms: new Set(["macos"]), + }); + }); + it("stops the host artifact server when the wrapper fails mid-run", async () => { let stopCalls = 0; const server: HostServer = { diff --git a/test/scripts/release-candidate-checklist.test.ts b/test/scripts/release-candidate-checklist.test.ts index e205b5071f19..9ad4c5f714bb 100644 --- a/test/scripts/release-candidate-checklist.test.ts +++ b/test/scripts/release-candidate-checklist.test.ts @@ -543,6 +543,7 @@ describe("release candidate checklist", () => { [".artifacts/preflight/openclaw-ai.tgz"], "/trusted", [".artifacts/preflight/openclaw-codex.tgz"], + "macOS 26.5 Node 24", ), ).toEqual([ "exec", @@ -554,6 +555,8 @@ describe("release candidate checklist", () => { ".artifacts/preflight/openclaw-ai.tgz", "--registry-package-tarball", ".artifacts/preflight/openclaw-codex.tgz", + "--macos-snapshot-hint", + "macOS 26.5 Node 24", "--json", ]); });