fix(test): sync sparse AWS Crabbox runs from full checkout

This commit is contained in:
Vincent Koc
2026-05-24 13:46:46 +02:00
parent 71547678c7
commit 0f82c810fc
3 changed files with 112 additions and 11 deletions

View File

@@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai
### Changes
### Fixes
- Crabbox: sync clean sparse-checkout remote changed gates from a temporary full checkout with local-only commits overlaid as worktree changes so git-backed script checks can seed the runner repository.
- Tests: make startup memory and startup bench smoke scripts build CLI startup artifacts when run from a fresh source checkout.
- iMessage: mark authorized slash-command turns as text-sourced commands so `/status`, `/new`, and `/restart` acknowledgements return to the source conversation. (#82642) thanks @homer-byte.
- Crabbox: install Corepack shims into the writable hydration `PNPM_HOME` so local AWS runner hydration no longer tries to overwrite `/usr/local/bin/pnpm`.

View File

@@ -426,8 +426,8 @@ function runCommandArgs(commandArgs) {
return start >= 0 ? commandArgs.slice(start) : [];
}
function commandRuntimeEntrypoint(commandArgs) {
const words = commandArgs.length === 1 ? commandArgs[0].split(/\s+/u) : commandArgs;
function normalizedCommandWords(commandArgs) {
const words = commandArgs.length === 1 ? commandArgs[0].split(/\s+/u) : [...commandArgs];
while (words[0] === "env") {
words.shift();
while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(words[0] ?? "")) {
@@ -437,13 +437,43 @@ function commandRuntimeEntrypoint(commandArgs) {
while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(words[0] ?? "")) {
words.shift();
}
const first = (words[0] ?? "")
.replace(/^['"]|['";|&()]+$/g, "")
.split("/")
.pop();
return words.map((word) => word.replace(/^['"]|['";|&()]+$/g, ""));
}
function commandRuntimeEntrypoint(commandArgs) {
const words = normalizedCommandWords(commandArgs);
const first = (words[0] ?? "").split("/").pop();
return ["pnpm", "npm", "npx", "corepack", "node", "yarn", "bun"].includes(first) ? first : "";
}
function isChangedGateCommand(commandArgs) {
const words = normalizedCommandWords(commandArgs);
if (words[0] === "corepack") {
words.shift();
}
return (
(words[0] === "pnpm" && words[1] === "check:changed") ||
(words[0] === "pnpm" && words[1] === "run" && words[2] === "check:changed") ||
(words[0] === "node" && (words[1] ?? "").endsWith("scripts/check-changed.mjs"))
);
}
function headInRemoteRefs() {
const refs = gitOutput([
"for-each-ref",
"--contains",
"HEAD",
"--format=%(refname)",
"refs/remotes",
]);
return refs.status === 0 && refs.stdout !== "";
}
function mergeBaseForChangedGate() {
const base = gitOutput(["merge-base", "origin/main", "HEAD"]);
return base.status === 0 && base.stdout ? base.stdout : "origin/main";
}
function isSparseCheckout() {
const config = gitOutput(["config", "--bool", "core.sparseCheckout"]);
if (config.status === 0 && config.stdout === "true") {
@@ -457,8 +487,8 @@ function isWorktreeClean() {
return gitOutput(["status", "--porcelain=v1"]).stdout === "";
}
function shouldUseFullCheckoutForCleanSparseBlacksmithSync(commandArgs, providerName) {
if (commandArgs[0] !== "run" || providerName !== "blacksmith-testbox") {
function shouldUseFullCheckoutForCleanSparseRemoteSync(commandArgs, providerName) {
if (commandArgs[0] !== "run" || isLocalContainerProvider(providerName)) {
return false;
}
if (
@@ -471,7 +501,7 @@ function shouldUseFullCheckoutForCleanSparseBlacksmithSync(commandArgs, provider
return isSparseCheckout() && isWorktreeClean();
}
function prepareFullCheckoutForSync() {
function prepareFullCheckoutForSync(options = {}) {
const dir = mkdtempSync(resolve(tmpdir(), "openclaw-crabbox-sync-"));
let active = false;
const add = gitOutput(["worktree", "add", "--detach", dir, "HEAD"]);
@@ -487,8 +517,17 @@ function prepareFullCheckoutForSync() {
throw new Error(`git sparse-checkout disable failed: ${disableSparse.text}`);
}
if (options.changedGateBase) {
const reset = gitOutput(["-C", dir, "reset", "--mixed", "--quiet", options.changedGateBase]);
if (reset.status !== 0) {
cleanupFullCheckout(dir, active);
throw new Error(`git reset for changed-gate sync failed: ${reset.text}`);
}
}
return {
dir,
changedGateBase: options.changedGateBase ?? "",
cleanup() {
cleanupFullCheckout(dir, active);
active = false;
@@ -645,13 +684,21 @@ if (provider === "blacksmith-testbox") {
let childCwd = repoRoot;
let cleanupChildCwd = () => {};
let cleanupDone = false;
if (shouldUseFullCheckoutForCleanSparseBlacksmithSync(args, provider)) {
const checkout = prepareFullCheckoutForSync();
if (shouldUseFullCheckoutForCleanSparseRemoteSync(args, provider)) {
const runWords = runCommandArgs(args);
const changedGateBase =
isChangedGateCommand(runWords) && !headInRemoteRefs() ? mergeBaseForChangedGate() : "";
const checkout = prepareFullCheckoutForSync({ changedGateBase });
childCwd = checkout.dir;
cleanupChildCwd = () => checkout.cleanup();
console.error(
`[crabbox] sparse clean checkout detected; syncing from temporary full checkout ${checkout.dir}`,
);
if (checkout.changedGateBase) {
console.error(
`[crabbox] remote changed gate detected; overlaying local HEAD as worktree changes from ${checkout.changedGateBase}`,
);
}
}
function cleanupOnce() {

View File

@@ -45,6 +45,7 @@ function makeFakeGit(responses: Record<string, { status?: number; stdout?: strin
"const args = process.argv.slice(2);",
"if (args[0] === 'worktree' && args[1] === 'add') { fs.mkdirSync(args[3], { recursive: true }); process.exit(0); }",
"if (args[0] === '-C' && args[2] === 'sparse-checkout' && args[3] === 'disable') { process.exit(0); }",
"if (args[0] === '-C' && args[2] === 'reset' && args[3] === '--mixed') { process.exit(0); }",
"if (args[0] === 'worktree' && args[1] === 'remove') { process.exit(0); }",
"const key = args.join('\\u0000');",
"const response = responses.get(key);",
@@ -248,6 +249,58 @@ describe("scripts/crabbox-wrapper", () => {
expect(parseFakeCrabboxOutput(result).cwd).toContain("openclaw-crabbox-sync-");
});
it("uses a temporary full checkout for clean sparse AWS syncs", () => {
const result = runWrapper(
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
["run", "--provider", "aws", "--", "corepack", "pnpm", "check:changed"],
{
gitResponses: {
["config\u0000--bool\u0000core.sparseCheckout"]: { stdout: "true\n" },
["status\u0000--porcelain=v1"]: { stdout: "" },
},
},
);
expect(result.status).toBe(0);
expect(result.stderr).toContain("syncing from temporary full checkout");
expect(result.stderr).toContain("overlaying local HEAD as worktree changes from origin/main");
expect(parseFakeCrabboxOutput(result).cwd).toContain("openclaw-crabbox-sync-");
});
it("keeps clean sparse local-container syncs on the original checkout", () => {
const result = runWrapper(
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
["run", "--provider", "local-container", "--", "echo ok"],
{
gitResponses: {
["config\u0000--bool\u0000core.sparseCheckout"]: { stdout: "true\n" },
["status\u0000--porcelain=v1"]: { stdout: "" },
},
},
);
expect(result.status).toBe(0);
expect(result.stderr).not.toContain("syncing from temporary full checkout");
expect(parseFakeCrabboxOutput(result).cwd).toBe(repoRoot);
});
it("keeps existing AWS leases on the original sparse checkout", () => {
const result = runWrapper(
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
["run", "--provider", "aws", "--id", "cbx_existing", "--", "echo ok"],
{
gitResponses: {
["config\u0000--bool\u0000core.sparseCheckout"]: { stdout: "true\n" },
["status\u0000--porcelain=v1"]: { stdout: "" },
},
},
);
expect(result.status).toBe(0);
expect(result.stderr).not.toContain("syncing from temporary full checkout");
expect(parseFakeCrabboxOutput(result).cwd).toBe(repoRoot);
});
it("uses a temporary full checkout when clean sparse branches differ from the Blacksmith ref", () => {
const result = runWrapper(
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",