From f20089c569478831086caf193f08a1e592203f0f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 14 Jul 2026 01:15:58 -0700 Subject: [PATCH] fix: route heavy changed checks remotely (#107282) --- scripts/check-changed.d.mts | 2 +- scripts/check-changed.mjs | 45 +++++++++++++++++++++--- test/scripts/changed-lanes.test.ts | 55 ++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 5 deletions(-) diff --git a/scripts/check-changed.d.mts b/scripts/check-changed.d.mts index 82398352b38a..31d526ef6f39 100644 --- a/scripts/check-changed.d.mts +++ b/scripts/check-changed.d.mts @@ -35,7 +35,7 @@ export function changedCheckRequiresRemote(result?: ChangedLaneResult): boolean; export function shouldDelegateChangedCheckToCrabbox( argv?: string[], env?: NodeJS.ProcessEnv, - options?: { cwd?: string; result?: ChangedLaneResult }, + options?: { cwd?: string; result?: ChangedLaneResult; diffRefsReady?: boolean }, ): boolean; export function buildChangedCheckCrabboxArgs(argv?: string[], options?: { cwd?: string }): string[]; export function shouldRunShrinkwrapGuard(paths: string[]): boolean; diff --git a/scripts/check-changed.mjs b/scripts/check-changed.mjs index 9df133323f6c..6e48816b457d 100644 --- a/scripts/check-changed.mjs +++ b/scripts/check-changed.mjs @@ -1,4 +1,5 @@ // Runs the changed-file check lanes selected by `scripts/changed-lanes.mjs`. +import { execFileSync } from "node:child_process"; import { accessSync, chmodSync, @@ -150,7 +151,16 @@ export function changedCheckLocalDependenciesReady(cwd = process.cwd()) { } export function changedCheckRequiresRemote(result) { - if (!result || result.paths.length === 0 || result.docsOnly) { + if (!result || result.paths.length === 0) { + return false; + } + if ( + shouldRunSqliteSessionSchemaBaselineCheck(result.paths) || + shouldRunPluginSdkApiBaselineCheck(result.paths) + ) { + return true; + } + if (result.docsOnly) { return false; } return Object.entries(result.lanes).some( @@ -168,21 +178,41 @@ export function shouldDelegateChangedCheckToCrabbox(argv = [], env = process.env if (argv.includes("--dry-run")) { return false; } - if (!options.result) { + const result = options.result; + if (!result) { return true; } - if (options.result.paths.length === 0) { + if (result.paths.length === 0) { return false; } if (isTruthyEnvFlag(env.OPENCLAW_TESTBOX)) { return true; } + // Release metadata plans diff the supplied commits after classification. A missing + // ref needs the hydrated remote checkout even when the explicit path itself is cheap. + if (result.lanes.releaseMetadata && options.diffRefsReady === false) { + return true; + } return ( - changedCheckRequiresRemote(options.result) || + changedCheckRequiresRemote(result) || !changedCheckLocalDependenciesReady(options.cwd ?? process.cwd()) ); } +function changedCheckDiffRefsReady({ base, head, cwd = process.cwd() }) { + for (const ref of [base, head]) { + try { + execFileSync("git", ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`], { + cwd, + stdio: "ignore", + }); + } catch { + return false; + } + } + return true; +} + export function buildChangedCheckCrabboxArgs(argv = [], options = {}) { const delegatedArgv = buildDelegatedChangedCheckArgv(argv, options); return [ @@ -954,6 +984,13 @@ if (isDirectRun()) { shouldDelegateChangedCheckToCrabbox(argv, process.env, { cwd: process.cwd(), result, + diffRefsReady: result.lanes.releaseMetadata + ? args.staged || + changedCheckDiffRefsReady({ + base: args.base, + head: args.head, + }) + : undefined, }) ) { process.exitCode = await runChangedCheckViaCrabbox(argv, process.env); diff --git a/test/scripts/changed-lanes.test.ts b/test/scripts/changed-lanes.test.ts index 7a17e2f4a84a..454b77cec675 100644 --- a/test/scripts/changed-lanes.test.ts +++ b/test/scripts/changed-lanes.test.ts @@ -289,6 +289,49 @@ describe("scripts/changed-lanes", () => { expect(result.stderr).not.toContain("ambiguous argument"); }); + it("delegates path-scoped release metadata when local diff refs are unavailable", () => { + const dir = makeTempRepoRoot(tempDirs, "openclaw-check-changed-metadata-missing-base-"); + git(dir, ["init", "-q", "--initial-branch=main"]); + writeFileSync(path.join(dir, "README.md"), "initial\n", "utf8"); + git(dir, ["add", "README.md"]); + git(dir, [ + "-c", + "user.email=test@example.com", + "-c", + "user.name=Test User", + "commit", + "-q", + "-m", + "initial", + ]); + writeRepoFile(dir, "node_modules/.modules.yaml", "layoutVersion: 5\n"); + writeRepoFile(dir, "node_modules/.bin/oxfmt", "#!/bin/sh\n"); + writeRepoFile(dir, "node_modules/typescript/package.json", '{"name":"typescript"}\n'); + const binDir = path.join(dir, "bin"); + mkdirSync(binDir, { recursive: true }); + writeFileSync(path.join(binDir, "pnpm"), "#!/bin/sh\nexit 0\n", { mode: 0o755 }); + + const result = spawnSync( + process.execPath, + [path.join(repoRoot, "scripts/check-changed.mjs"), "--", "CHANGELOG.md"], + { + cwd: dir, + encoding: "utf8", + env: { + ...createNestedGitEnv(), + CI: "", + GITHUB_ACTIONS: "", + OPENCLAW_CHECK_CHANGED_REMOTE_CHILD: "", + OPENCLAW_TESTBOX: "", + PATH: `${binDir}:${process.env.PATH ?? ""}`, + }, + }, + ); + + expect(result.status).toBe(0); + expect(result.stderr).toContain("delegating to Blacksmith Testbox"); + }); + it("rejects unknown changed lane options before treating them as paths", () => { const result = spawnSync(process.execPath, ["scripts/changed-lanes.mjs", "--jsno"], { cwd: repoRoot, @@ -1123,6 +1166,18 @@ describe("scripts/changed-lanes", () => { expect(changedCheckRequiresRemote(mixedResult)).toBe(true); }); + it("delegates generated docs baselines with heavy owner checks", () => { + for (const changedPath of [ + "docs/.generated/plugin-sdk-api-baseline.sha256", + "docs/.generated/sqlite-session-transcript-schema-baseline.sha256", + ]) { + const result = detectChangedLanes([changedPath]); + expect(result.docsOnly).toBe(true); + expect(changedCheckRequiresRemote(result)).toBe(true); + expect(shouldDelegateChangedCheckToCrabbox([], {}, { cwd: repoRoot, result })).toBe(true); + } + }); + it("delegates staged changed gates as explicit remote paths", () => { const dir = makeTempRepoRoot(tempDirs, "openclaw-check-changed-staged-delegate-"); git(dir, ["init", "-q", "--initial-branch=main"]);