Files
openclaw/test/scripts/crabbox-wrapper.test.ts
Peter Steinberger ce19b0b484 test(crabbox): reuse prepared toolchain cache (#158850)
Keep the existing ten-second receive bound and exercise real pnpm with Corepack networking disabled after preparation.
2026-09-26 05:40:37 -07:00

7008 lines
289 KiB
TypeScript

import {
spawn,
spawnSync,
type ChildProcess,
type SpawnSyncOptionsWithStringEncoding,
} from "node:child_process";
import { createHash } from "node:crypto";
import {
chmodSync,
copyFileSync,
cpSync,
existsSync,
lstatSync,
mkdirSync,
mkdtempSync,
readFileSync,
readlinkSync,
readdirSync,
realpathSync,
renameSync,
rmSync,
symlinkSync,
writeFileSync,
} from "node:fs";
import { constants as osConstants, homedir, tmpdir } from "node:os";
import path from "node:path";
import { DatabaseSync } from "node:sqlite";
import { setTimeout as delay } from "node:timers/promises";
import { pathToFileURL } from "node:url";
import { getSystemErrorMap } from "node:util";
import { build, type BuildOptions } from "esbuild";
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
import { parse } from "yaml";
import {
isProviderAdvertised,
parseProvidersFromHelp,
} from "../../scripts/crabbox-wrapper-providers.mts";
import { pnpmLockfileDocuments } from "../../scripts/lib/pnpm-lockfile-documents.mjs";
import { resolvePnpmRunner } from "../../scripts/pnpm-runner.mts";
import { spawnTerminalPty } from "../../src/process/terminal-pty.js";
import { resolveTestNodeExecPath } from "../../src/test-utils/node-process.js";
import { isProcessAlive } from "../helpers/process-wait.js";
import { makeTempDir, useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
import { withShimFixture } from "./direct-run-entrypoints.test-support.js";
const tempDirs: string[] = [];
const invocationLogTempDirs = useAutoCleanupTempDirTracker(afterEach);
const artifactTempDirs = useAutoCleanupTempDirTracker(afterEach);
const dependencyTempDirs = useAutoCleanupTempDirTracker(afterAll);
const repoRoot = process.cwd();
const bundledWrapperPath = path.join(repoRoot, ".tmp", `crabbox-wrapper-test-${process.pid}.mjs`);
const realBundledWrapperPath = bundledWrapperPath.replace(".mjs", "-real.mjs");
let bundledSetupPath: string;
let preparedDependencyRoot: string | undefined;
const fakeCrabboxBinDirs = new Map<string, string>();
const fakeGitBinDirs = new Map<string, string>();
const timingPreloads = new Map<string, string>();
const GIT_COMMON_DIR_KEY = "rev-parse\u0000--git-common-dir";
const GIT_CONFIG_SPARSE_KEY = "config\u0000--bool\u0000core.sparseCheckout";
const GIT_SPARSE_LIST_KEY = "sparse-checkout\u0000list";
const GIT_STATUS_PORCELAIN_KEY = "status\u0000--porcelain=v1";
const GIT_MERGE_BASE_MAIN_HEAD_KEY = "merge-base\u0000origin/main\u0000HEAD";
const GIT_MERGE_BASE_RELEASE_HEAD_KEY = "merge-base\u0000origin/release/2026.7.2\u0000HEAD";
const GIT_CHECK_RELEASE_REF_KEY = "check-ref-format\u0000refs/remotes/origin/release/2026.7.2";
const defaultProviderHelp =
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n";
const brokerProviderHelp = "provider: aws, azure, blacksmith-testbox, or daytona\n";
const azureProviderHelp =
"provider: hetzner, aws, azure, local-container, blacksmith-testbox, or cloudflare\n";
const fakeRunValueOptionHelp = [
"artifact-glob value",
"blacksmith-ref string",
"capture-stderr string",
"capture-stdout string",
"download value",
"fresh-pr string",
"id string",
"idle-timeout duration",
"label string",
"market string",
"provider string",
"require-artifact value",
"script string",
"target string",
"ttl duration",
"windows-mode string",
]
.map((option) => ` -${option}\n`)
.join("");
const fakeWarmupValueOptionHelp = `${fakeRunValueOptionHelp} -lease-id string\n`;
const fakeHydrateValueOptionHelp = `${fakeRunValueOptionHelp} -field value\n -job string\n`;
const defaultGitResponses: Record<string, { status?: number; stdout?: string; stderr?: string }> = {
[GIT_CONFIG_SPARSE_KEY]: { stdout: "false\n" },
[GIT_SPARSE_LIST_KEY]: { status: 1 },
};
const remoteTestboxBootstrap = "export CI=true;";
function makeFakeCrabbox(helpText: string): string {
const cached = fakeCrabboxBinDirs.get(helpText);
if (cached) {
return cached;
}
const binDir = mkdtempSync(path.join(tmpdir(), "openclaw-fake-crabbox-"));
tempDirs.push(binDir);
writeFakeCrabbox(binDir, helpText);
fakeCrabboxBinDirs.set(helpText, binDir);
return binDir;
}
function writeFakeCrabbox(binDir: string, helpText: string): string {
mkdirSync(binDir, { recursive: true });
const crabboxPath = path.join(binDir, "crabbox");
const stampClaimScript = [
"const claimPaths = [process.env.OPENCLAW_FAKE_CRABBOX_CLAIM_PATH, process.env.OPENCLAW_FAKE_CRABBOX_EXTRA_CLAIM_PATH].filter(Boolean);",
"for (const claimPath of claimPaths) { const claim = fs.existsSync(claimPath) ? JSON.parse(fs.readFileSync(claimPath, 'utf8')) : { leaseID: process.env.OPENCLAW_FAKE_CRABBOX_TIMING_LEASE_ID }; claim.repoRoot = process.env.OPENCLAW_FAKE_CRABBOX_CLAIM_REPO_ROOT || process.cwd(); fs.mkdirSync(path.dirname(claimPath), { recursive: true }); fs.writeFileSync(claimPath, JSON.stringify(claim) + '\\n', 'utf8'); }",
"if (process.env.OPENCLAW_FAKE_CRABBOX_TIMING_LEASE_ID) process.stderr.write(JSON.stringify({ provider: 'blacksmith-testbox', leaseId: process.env.OPENCLAW_FAKE_CRABBOX_TIMING_LEASE_ID, exitCode: 0 }) + '\\n');",
].join("");
// Keep the descendant in the fake's process group, and publish readiness only
// after its signal handlers exist so the wrapper's group cleanup is deterministic.
const signalIgnoringDescendantScript = String.raw`
import fs from "node:fs";
let finishing = false;
for (const signal of ["SIGHUP", "SIGINT", "SIGTERM"]) {
process.on(signal, () => {
const artifact = process.env.OPENCLAW_FAKE_CRABBOX_DELAYED_ARTIFACT;
if (!artifact || finishing) return;
finishing = true;
setTimeout(() => {
fs.writeFileSync(artifact, "graceful shutdown\n");
process.exit(0);
}, 150);
});
}
const pidPath = process.env.OPENCLAW_FAKE_CRABBOX_DESCENDANT_PID_PATH;
const tmpPath = pidPath + ".tmp." + process.pid;
fs.writeFileSync(tmpPath, String(process.pid));
fs.renameSync(tmpPath, pidPath);
if (process.send) { process.send("ready"); process.disconnect(); }
const release = process.env.OPENCLAW_FAKE_CRABBOX_ESCAPED_RELEASE_PATH;
if (release) {
setInterval(() => {
if (!fs.existsSync(release)) return;
fs.writeFileSync(release + ".read", fs.readFileSync("fixture.txt"));
process.exit(0);
}, 10);
} else {
setInterval(() => {}, 1000);
}
`;
// The two cwd-loss modes distinguish active-child monitoring from the post-exit
// guard; both must chdir away before deleting the temporary checkout.
const script = String.raw`
const fs = require("node:fs"); const path = require("node:path"); const { spawn, execFileSync } = require("node:child_process");
const args = process.argv.slice(2); const helpText = ${JSON.stringify(`${helpText}${fakeRunValueOptionHelp}`)};
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const optionValue = (name) => {
const index = args.findIndex((arg) => arg === "--" + name || arg === "-" + name); const assigned = args.find((arg) => arg.startsWith("--" + name + "=") || arg.startsWith("-" + name + "="));
return index >= 0 ? args[index + 1] || "" : assigned?.slice(assigned.indexOf("=") + 1) || "";
};
async function main() {
if (process.env.OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG) fs.appendFileSync(process.env.OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG, JSON.stringify(args) + "\n");
if (args[0] === "sync-plan") {
const ready = process.env.OPENCLAW_FAKE_CRABBOX_SYNC_PLAN_READY_PATH;
if (ready) {
const temporary = ready + ".tmp." + process.pid;
fs.writeFileSync(temporary, JSON.stringify({ pid: process.pid, parentPid: process.ppid, cwd: process.cwd() }));
fs.renameSync(temporary, ready);
await new Promise(() => setInterval(() => {}, 1000));
}
const excluded = new Set(JSON.parse(process.env.OPENCLAW_FAKE_CRABBOX_PRIVACY_PATHS || "[]"));
const candidates = execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], { encoding: "utf8" }).split("\0").filter(Boolean);
const topFiles = [...new Set(candidates)].filter((file) => !excluded.has(file) && fs.existsSync(path.dirname(file)) && (() => { try { return !fs.lstatSync(file).isDirectory(); } catch { return false; } })()).map((file) => ({ path: file }));
if (process.env.OPENCLAW_FAKE_CRABBOX_SELECTION_UNKNOWN_PATH) topFiles.push({ path: "not-a-source-candidate.txt" });
process.stdout.write(JSON.stringify({ candidate: { files: topFiles.length + Number(process.env.OPENCLAW_FAKE_CRABBOX_SELECTION_COUNT_DELTA || "0") }, topFiles })); return;
}
if (args[0] === "--version") { console.log(process.env.OPENCLAW_FAKE_CRABBOX_VERSION || "crabbox 0.56.0"); return; }
if (args[0] === "run" && args[1] === "--help") { process.stdout.write(helpText); return; }
if (args[0] === "warmup" && args[1] === "--help") { process.stdout.write(${JSON.stringify(`${helpText}${fakeWarmupValueOptionHelp}`)}); return; }
if (args[0] === "actions" && args[1] === "hydrate" && args[2] === "--help") { process.stdout.write(${JSON.stringify(`${helpText}${fakeHydrateValueOptionHelp}`)}); return; }
if (args[0] === "doctor") {
const provider = optionValue("provider"); const target = optionValue("target"); const windowsMode = optionValue("windows-mode");
if (process.env.OPENCLAW_FAKE_CRABBOX_DOCTOR_PROGRESS) process.stderr.write(process.env.OPENCLAW_FAKE_CRABBOX_DOCTOR_PROGRESS + "\n");
await wait(Number.parseInt(process.env.OPENCLAW_FAKE_CRABBOX_DOCTOR_DELAY_MS || "0", 10));
if (process.env.OPENCLAW_FAKE_CRABBOX_EXPECT_DOCTOR_TARGET && target !== process.env.OPENCLAW_FAKE_CRABBOX_EXPECT_DOCTOR_TARGET) { process.stderr.write("doctor target mismatch: got=" + target + "\n"); process.exit(64); }
if (process.env.OPENCLAW_FAKE_CRABBOX_EXPECT_DOCTOR_WINDOWS_MODE && windowsMode !== process.env.OPENCLAW_FAKE_CRABBOX_EXPECT_DOCTOR_WINDOWS_MODE) { process.stderr.write("doctor windows mode mismatch: got=" + windowsMode + "\n"); process.exit(64); }
const malformed = new Set((process.env.OPENCLAW_FAKE_CRABBOX_MALFORMED_DOCTOR_PROVIDERS || "").split(",").filter(Boolean));
if (malformed.has(provider)) { process.stdout.write("{not-json\n"); process.exit(1); }
const invalid = new Set((process.env.OPENCLAW_FAKE_CRABBOX_INVALID_DOCTOR_PROVIDERS || "").split(",").filter(Boolean));
if (invalid.has(provider)) { process.stdout.write(JSON.stringify({ ok: true, provider, checks: [{ status: "ok" }] }) + "\n"); return; }
const mismatched = new Set((process.env.OPENCLAW_FAKE_CRABBOX_MISMATCHED_DOCTOR_PROVIDERS || "").split(",").filter(Boolean));
if (mismatched.has(provider)) { process.stdout.write(JSON.stringify({ ok: true, provider: "wrong-provider", checks: [{ status: "ok", check: "broker" }] }) + "\n"); return; }
const inconsistent = new Set((process.env.OPENCLAW_FAKE_CRABBOX_INCONSISTENT_DOCTOR_PROVIDERS || "").split(",").filter(Boolean));
if (inconsistent.has(provider)) { process.stdout.write(JSON.stringify({ ok: true, provider, checks: [{ status: "ok", check: "broker" }] }) + "\n"); process.exit(1); }
const managed = new Set(["aws", "azure", "daytona"]).has(provider);
const missingBroker = new Set((process.env.OPENCLAW_FAKE_CRABBOX_MISSING_BROKER_PROVIDERS || "").split(",").filter(Boolean));
const providerUnauthorized = new Set((process.env.OPENCLAW_FAKE_CRABBOX_PROVIDER_UNAUTHORIZED_PROVIDERS || "").split(",").filter(Boolean));
if (providerUnauthorized.has(provider)) { process.stdout.write(JSON.stringify({ ok: false, provider, checks: [{ status: "ok", check: "broker" }, { status: "failed", check: "provider", message: "class=broker_auth hint=crabbox_login unauthorized", details: { class: "broker_auth", hint: "crabbox_login" } }] }) + "\n"); process.exit(1); }
const unclassifiedUnauthorized = new Set((process.env.OPENCLAW_FAKE_CRABBOX_UNCLASSIFIED_UNAUTHORIZED_PROVIDERS || "").split(",").filter(Boolean));
if (unclassifiedUnauthorized.has(provider)) { process.stdout.write(JSON.stringify({ ok: false, provider, checks: [{ status: "failed", check: "broker", message: "coordinator GET /v1/whoami: http 401: unauthorized" }] }) + "\n"); process.exit(1); }
const unauthorized = new Set((process.env.OPENCLAW_FAKE_CRABBOX_UNAUTHORIZED_PROVIDERS || "").split(",").filter(Boolean));
if (unauthorized.has(provider)) { process.stdout.write(JSON.stringify({ ok: false, provider, checks: [{ status: "failed", check: "broker", message: "class=broker_auth hint=crabbox_login unauthorized", details: { class: "broker_auth", hint: "crabbox_login" } }] }) + "\n"); process.exit(1); }
const unready = new Set((process.env.OPENCLAW_FAKE_CRABBOX_UNREADY_PROVIDERS || "").split(",").filter(Boolean));
const ready = !unready.has(provider) && (!managed || !missingBroker.has(provider)); const checks = [];
if (managed && !missingBroker.has(provider)) checks.push({ status: "ok", check: "broker", details: { auth: "token" } });
checks.push({ status: ready ? "ok" : "failed", check: "provider", details: { provider } });
process.stdout.write(JSON.stringify({ ok: ready, provider, checks }) + "\n");
process.exit(ready ? 0 : 1);
}
if (args[0] === "run" || args[0] === "warmup") { ${stampClaimScript} }
if (args[0] === "run" && process.env.OPENCLAW_FAKE_CRABBOX_RUN_IDENTITY_PATH) fs.writeFileSync(process.env.OPENCLAW_FAKE_CRABBOX_RUN_IDENTITY_PATH, JSON.stringify({ pid: process.pid, parentPid: process.ppid, cwd: process.cwd() }));
if (args[0] === "run" && process.env.OPENCLAW_FAKE_CRABBOX_ARTIFACT_LINKS) {
for (const [file, target] of Object.entries(JSON.parse(process.env.OPENCLAW_FAKE_CRABBOX_ARTIFACT_LINKS))) { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.symlinkSync(target, file); }
}
if (args[0] === "run" && process.env.OPENCLAW_FAKE_CRABBOX_ARTIFACTS) {
for (const [file, bytes] of Object.entries(JSON.parse(process.env.OPENCLAW_FAKE_CRABBOX_ARTIFACTS))) { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, Buffer.from(bytes, "base64")); }
}
if (args[0] === "run" && process.env.OPENCLAW_FAKE_CRABBOX_ARTIFACT_FIFO) execFileSync("mkfifo", [process.env.OPENCLAW_FAKE_CRABBOX_ARTIFACT_FIFO]);
if (args[0] === "run" && process.env.OPENCLAW_FAKE_CRABBOX_CORRUPT_CLAIM === "1") fs.writeFileSync(process.env.OPENCLAW_FAKE_CRABBOX_CLAIM_PATH, "{corrupt claim");
const runStatus = Number.parseInt(process.env.OPENCLAW_FAKE_CRABBOX_RUN_STATUS || "0", 10); if (args[0] === "run" && runStatus !== 0) { process.stdout.write(JSON.stringify({ args, cwd: process.cwd() }) + "\n"); process.stderr.write("fake run failure\n"); process.exit(runStatus); }
if (args[0] === "config" && args[1] === "show" && args.includes("--json")) {
const status = Number.parseInt(process.env.OPENCLAW_FAKE_CRABBOX_CONFIG_STATUS || "0", 10);
if (status !== 0) { process.stderr.write("config unavailable\n"); process.exit(status); }
process.stdout.write(Object.hasOwn(process.env, "OPENCLAW_FAKE_CRABBOX_CONFIG_JSON") ? process.env.OPENCLAW_FAKE_CRABBOX_CONFIG_JSON : '{"coordinator":"configured-broker","brokerMode":"managed","brokerAuth":"configured"}');
return;
}
if (args[0] === "whoami") {
await wait(Number.parseInt(process.env.OPENCLAW_FAKE_CRABBOX_WHOAMI_DELAY_MS || "0", 10));
const status = Number.parseInt(process.env.OPENCLAW_FAKE_CRABBOX_WHOAMI_STATUS || "0", 10);
if (status !== 0) { process.stderr.write('coordinator GET /v1/whoami: http 401: {"error":"unauthorized"}\n'); process.exit(status); }
process.stdout.write("fake-crabbox-user\n"); return;
}
const scriptIndex = args.findIndex((arg) => arg === "--script" || arg === "-script"); const scriptPath = scriptIndex >= 0 ? args[scriptIndex + 1] : "";
const scriptContent = scriptPath ? fs.readFileSync(scriptPath, "utf8") : "";
if (process.env.OPENCLAW_FAKE_CRABBOX_DELETE_CWD_AND_EXIT === "1") {
await wait(100); const deletedCwd = process.cwd(); process.chdir(path.parse(deletedCwd).root || "/");
fs.rmSync(deletedCwd, { recursive: true, force: true }); process.exit(0);
}
if (process.env.OPENCLAW_FAKE_CRABBOX_DELETE_CWD_ONCE === "1") {
const deletedCwd = process.cwd(); process.chdir(path.parse(deletedCwd).root || "/");
fs.rmSync(deletedCwd, { recursive: true, force: true }); let attempts = 1000;
while (attempts-- > 0 && !fs.existsSync(deletedCwd)) await wait(10);
if (!fs.existsSync(deletedCwd)) { process.stderr.write("cwd was not restored: " + deletedCwd + "\n"); process.exit(66); }
process.chdir(deletedCwd);
}
if (process.env.OPENCLAW_FAKE_CRABBOX_DESCENDANT_PID_PATH) {
const escaped = Boolean(process.env.OPENCLAW_FAKE_CRABBOX_ESCAPED_RELEASE_PATH);
const child = spawn(process.execPath, ["--input-type=module", "--eval", ${JSON.stringify(signalIgnoringDescendantScript)}], { detached: escaped, stdio: escaped ? ["ignore", "ignore", "inherit", "ipc"] : "ignore" });
if (escaped) child.once("message", () => process.exit(0));
else setInterval(() => {}, 1000);
return;
}
const bundlePath = ".openclaw-crabbox-changed-gate.bundle";
if (process.env.OPENCLAW_FAKE_CRABBOX_NATIVE_SYNC_CAPTURE) {
const git = (args) => execFileSync("git", args, { encoding: "utf8" }).trim();
const changed = git(["diff", "--name-only", "--no-renames", "main"]).split("\n").filter(Boolean);
fs.writeFileSync(process.env.OPENCLAW_FAKE_CRABBOX_NATIVE_SYNC_CAPTURE, JSON.stringify({
base: git(["rev-parse", "main"]), source: git(["rev-parse", "HEAD"]),
head: fs.readFileSync(".git/HEAD", "utf8").trim(),
changed,
}));
}
if (process.env.OPENCLAW_FAKE_CRABBOX_COPY_CHANGED_GATE_BUNDLE_TO) fs.copyFileSync(bundlePath, process.env.OPENCLAW_FAKE_CRABBOX_COPY_CHANGED_GATE_BUNDLE_TO);
process.stdout.write(JSON.stringify({ args, cwd: process.cwd(), scriptContent }) + "\n");
}
main().catch((error) => { process.stderr.write(String(error?.stack || error) + "\n"); process.exit(1); });`;
if (process.platform === "win32") {
writeNodeCommand(crabboxPath, script);
} else {
const nodePath = `${crabboxPath}-node`;
const runHelpText = `${helpText}${fakeRunValueOptionHelp}`;
writeNodeCommand(nodePath, script);
writeShellCommand(
crabboxPath,
[
'if [ "$#" -eq 1 ] && [ "$1" = "--version" ]; then',
' if [ -n "${OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG:-}" ]; then',
` printf '%s\\n' '["--version"]' >> "$OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG"`,
" fi",
` printf '%s\\n' "\${OPENCLAW_FAKE_CRABBOX_VERSION:-crabbox 0.56.0}"`,
" exit 0",
"fi",
'if [ "$#" -eq 2 ] && [ "$1" = "run" ] && [ "$2" = "--help" ]; then',
` printf '%s' ${shellQuote(runHelpText)}`,
" exit 0",
"fi",
'if [ "$#" -eq 2 ] && [ "$1" = "warmup" ] && [ "$2" = "--help" ]; then',
` printf '%s' ${shellQuote(`${helpText}${fakeWarmupValueOptionHelp}`)}`,
" exit 0",
"fi",
'if [ "$#" -eq 3 ] && [ "$1" = "actions" ] && [ "$2" = "hydrate" ] && [ "$3" = "--help" ]; then',
` printf '%s' ${shellQuote(`${helpText}${fakeHydrateValueOptionHelp}`)}`,
" exit 0",
"fi",
"fast_run=1",
'for arg in "$@"; do',
' case "$arg" in --artifact-glob|-artifact-glob|--script|-script) fast_run=0 ;; esac',
"done",
'if { [ "$1" = "run" ] || [ "$1" = "warmup" ]; } && [ "$fast_run" -eq 1 ] &&',
' [ -z "${OPENCLAW_FAKE_CRABBOX_CLAIM_PATH:-}${OPENCLAW_FAKE_CRABBOX_EXTRA_CLAIM_PATH:-}${OPENCLAW_FAKE_CRABBOX_TIMING_LEASE_ID:-}${OPENCLAW_FAKE_CRABBOX_RUN_STATUS:-}${OPENCLAW_FAKE_CRABBOX_DELETE_CWD_AND_EXIT:-}${OPENCLAW_FAKE_CRABBOX_DELETE_CWD_ONCE:-}${OPENCLAW_FAKE_CRABBOX_DESCENDANT_PID_PATH:-}${OPENCLAW_FAKE_CRABBOX_RUN_IDENTITY_PATH:-}${OPENCLAW_FAKE_CRABBOX_COPY_CHANGED_GATE_BUNDLE_TO:-}" ]; then',
` printf '${fakeCrabboxProtocol}\\000%s\\000' "$#"`,
" printf '%s\\000' \"$@\"",
" printf '%s\\000\\000' \"$PWD\"",
" exit 0",
"fi",
`exec node ${shellQuote(nodePath)} "$@"`,
].join("\n"),
);
}
return crabboxPath;
}
// Help probes tolerate slow startup and either output stream.
function makeSlowHelpCrabbox(helpText: string, delayMs: number): string {
const binDir = mkdtempSync(path.join(tmpdir(), "openclaw-slow-help-crabbox-"));
tempDirs.push(binDir);
const crabboxPath = path.join(binDir, "crabbox");
const runHelpText = `${helpText}${fakeRunValueOptionHelp}`;
writeNodeCommand(
crabboxPath,
String.raw`
const args = process.argv.slice(2);
if (args[0] === "--version") {
console.log("crabbox 0.56.0");
} else if (args[0] === "run" && args[1] === "--help") {
setTimeout(() => { process.stderr.write(${JSON.stringify(runHelpText)}); process.exit(0); }, ${delayMs});
}`,
);
return binDir;
}
function testTimingPreload(options: { clockScale?: number; spawnTimeoutMs?: number }): string {
const key = JSON.stringify(options);
let preloadPath = timingPreloads.get(key);
if (!preloadPath) {
const dir = mkdtempSync(path.join(tmpdir(), "openclaw-crabbox-timing-"));
tempDirs.push(dir);
preloadPath = path.join(dir, "preload.cjs");
const script: string[] = [];
if (options.clockScale !== undefined) {
script.push(
"const realNow = Date.now.bind(Date);",
"const startedAt = realNow();",
`Date.now = () => startedAt + (realNow() - startedAt) * ${options.clockScale};`,
);
}
if (options.spawnTimeoutMs !== undefined) {
script.push(
'const childProcess = require("node:child_process");',
'const { syncBuiltinESMExports } = require("node:module");',
"const realSpawnSync = childProcess.spawnSync;",
"childProcess.spawnSync = (command, args, spawnOptions) =>",
" realSpawnSync(command, args,",
` spawnOptions?.timeout ? { ...spawnOptions, timeout: Math.min(spawnOptions.timeout, ${options.spawnTimeoutMs}) } : spawnOptions);`,
"syncBuiltinESMExports();",
);
}
writeFileSync(preloadPath, `${script.join("\n")}\n`, "utf8");
timingPreloads.set(key, preloadPath);
}
return preloadPath;
}
function windowsNodeCmdShim(target: string): string {
return [
"@ECHO off",
"GOTO start",
":find_dp0",
"SET dp0=%~dp0",
"EXIT /b",
":start",
"SETLOCAL",
"CALL :find_dp0",
'IF EXIST "%dp0%\\node.exe" (',
' SET "_prog=%dp0%\\node.exe"',
") ELSE (",
' SET "_prog=node"',
")",
"",
'endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & set PATHEXT=%PATHEXT:;.JS;=;% & "%_prog%" "%dp0%\\' +
target +
'" %*',
"",
].join("\r\n");
}
function shellQuote(value: string): string {
return `'${value.replaceAll("'", `'"'"'`)}'`;
}
function writeShellCommand(commandPath: string, script: string): void {
writeFileSync(commandPath, `#!/bin/sh\n${script.trimStart()}\n`, "utf8");
chmodSync(commandPath, 0o755);
}
function writeNodeCommand(commandPath: string, script: string): void {
writeFileSync(commandPath, `#!/usr/bin/env node\n${script.trimStart()}\n`, "utf8");
if (process.platform === "win32") {
const shim = windowsNodeCmdShim(path.basename(commandPath));
writeFileSync(`${commandPath}.cmd`, shim, "utf8");
}
chmodSync(commandPath, 0o755);
}
function makeFakeGit(
responses: Record<string, { status?: number; stdout?: string; stderr?: string }>,
): string {
const key = JSON.stringify(responses);
const cached = fakeGitBinDirs.get(key);
if (cached) {
return cached;
}
const binDir = mkdtempSync(path.join(tmpdir(), "openclaw-fake-git-"));
tempDirs.push(binDir);
const gitPath = path.join(binDir, "git");
const script = String.raw`
const fs = require("node:fs"); const path = require("node:path"); const args = process.argv.slice(2);
if (args[0] === "rev-parse" && args[1] === "--show-toplevel") {
process.stdout.write(fs.realpathSync(process.cwd()) + "\n"); process.exit(0);
}
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] === "rev-parse") {
const value = args[3] === "HEAD" ? process.env.OPENCLAW_FAKE_GIT_HEAD_SHA || "def456" : args[3] === "HEAD^{tree}" ? process.env.OPENCLAW_FAKE_GIT_HEAD_TREE_SHA || "tree456" : args[3].endsWith("^{tree}") ? process.env.OPENCLAW_FAKE_GIT_BASE_TREE_SHA || "base-tree123" : process.env.OPENCLAW_FAKE_GIT_BASE_SHA || "abc123";
process.stdout.write(value + "\n"); process.exit(0);
}
if (args[0] === "worktree" && args[1] === "remove") { fs.rmSync(args[3], { recursive: true, force: true }); process.exit(0); }
const response = new Map(Object.entries(JSON.parse(process.env.OPENCLAW_FAKE_GIT_RESPONSES || "{}"))).get(args.join("\u0000"));
if (!response) process.exit(1);
if (response.stdout) process.stdout.write(response.stdout); if (response.stderr) process.stderr.write(response.stderr);
process.exit(response.status ?? 0);`;
if (process.platform === "win32") {
writeNodeCommand(gitPath, script);
} else {
const nodePath = `${gitPath}-node`;
const responseCases = Object.entries(responses).map(([command, response]) => {
const args = command.split("\u0000");
const condition = [
`[ "$#" -eq ${args.length} ]`,
...args.map((arg, index) => `[ "$${index + 1}" = ${shellQuote(arg)} ]`),
].join(" && ");
return [
`if ${condition}; then`,
...(response.stdout ? [` printf '%s' ${shellQuote(response.stdout)}`] : []),
...(response.stderr ? [` printf '%s' ${shellQuote(response.stderr)} >&2`] : []),
` exit ${response.status ?? 0}`,
"fi",
].join("\n");
});
writeNodeCommand(nodePath, script);
writeShellCommand(
gitPath,
`${responseCases.join("\n")}\nexec node ${shellQuote(nodePath)} "$@"`,
);
}
fakeGitBinDirs.set(key, binDir);
return binDir;
}
function runWrapper(helpText: string, args: string[], options: WrapperOptions = {}) {
const nodeArgs = [
...(options.nodePreload ? ["--require", options.nodePreload] : []),
bundledWrapperPath,
...args,
];
return spawnSync(process.execPath, nodeArgs, {
cwd: repoRoot,
encoding: "utf8",
input: options.input,
env: wrapperEnv(helpText, options),
timeout: options.timeoutMs ?? 10_000,
});
}
function runDefaultWrapper(args: string[], options: WrapperOptions = {}) {
return runWrapper(defaultProviderHelp, args, options);
}
type WrapperOptions = {
configJson?: Record<string, unknown>;
configStatus?: number;
env?: Record<string, string>;
extraPathEntries?: string[];
gitResponses?: Record<string, { status?: number; stdout?: string; stderr?: string }>;
input?: string;
nodePreload?: string;
timeoutMs?: number;
};
function wrapperEnv(helpText: string, options: WrapperOptions): NodeJS.ProcessEnv {
const binDir = makeFakeCrabbox(helpText);
const gitResponses = { ...defaultGitResponses, ...options.gitResponses };
const gitBinDir = makeFakeGit(gitResponses);
return {
...process.env,
PATH: [...(options.extraPathEntries ?? []), binDir, gitBinDir, process.env.PATH ?? ""]
.filter(Boolean)
.join(path.delimiter),
CRABBOX_PROVIDER: "",
CRABBOX_TARGET: "",
CRABBOX_TARGET_OS: "",
CRABBOX_WINDOWS_MODE: "",
OPENCLAW_CRABBOX_ALLOW_DIRECT_AWS: "",
OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES: "0",
OPENCLAW_CRABBOX_WRAPPER_IGNORE_REPO_BINARY: "1",
...(options.configJson
? {
OPENCLAW_FAKE_CRABBOX_CONFIG_JSON: JSON.stringify({
brokerMode: "managed",
...options.configJson,
}),
}
: {}),
...(options.configStatus
? { OPENCLAW_FAKE_CRABBOX_CONFIG_STATUS: String(options.configStatus) }
: {}),
...options.env,
OPENCLAW_FAKE_GIT_RESPONSES: JSON.stringify(gitResponses),
};
}
type FakeCrabboxOutput = {
args: string[];
cwd: string;
scriptContent?: string;
};
const fakeCrabboxProtocol = "OPENCLAW_FAKE_CRABBOX_V1";
function parseFakeCrabboxOutput(result: ReturnType<typeof runWrapper>): FakeCrabboxOutput {
if (result.stdout.startsWith(`${fakeCrabboxProtocol}\0`)) {
const fields = result.stdout.split("\0");
const count = Number(fields[1]);
return {
args: fields.slice(2, 2 + count),
cwd: fields[2 + count] ?? "",
scriptContent: fields[3 + count] ?? "",
};
}
return JSON.parse(result.stdout.trim()) as FakeCrabboxOutput;
}
function makeInvocationLog(): string {
const dir = invocationLogTempDirs.make("openclaw-crabbox-invocations-");
return path.join(dir, "invocations.jsonl");
}
function readInvocations(logPath: string): string[][] {
return readFileSync(logPath, "utf8")
.trim()
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line) as string[]);
}
type ParsedWrapperRun = {
output: ReturnType<typeof parseFakeCrabboxOutput>;
remoteCommand: string;
result: ReturnType<typeof runWrapper>;
};
function expectSuccessfulWrapperRun(result: ReturnType<typeof runWrapper>): ParsedWrapperRun {
expect(result.status, result.stderr).toBe(0);
const output = parseFakeCrabboxOutput(result);
const remoteCommand = normalizeShellLineEndings(output.args.at(-1) ?? "");
return { output, remoteCommand, result };
}
function runSuccessfulDefaultWrapper(args: string[], options: WrapperOptions = {}) {
return expectSuccessfulWrapperRun(runDefaultWrapper(args, options));
}
function runSuccessfulWrapper(helpText: string, args: string[], options: WrapperOptions = {}) {
return expectSuccessfulWrapperRun(runWrapper(helpText, args, options));
}
function runBrokerWrapper(args: string[], options: WrapperOptions = {}) {
return runWrapper(brokerProviderHelp, args, {
...options,
env: options.env,
});
}
function runSuccessfulBrokerWrapper(args: string[], options: WrapperOptions = {}) {
return expectSuccessfulWrapperRun(runBrokerWrapper(args, options));
}
function managedBrokerConfig(
provider: string,
overrides: Record<string, unknown> = {},
): Record<string, unknown> {
return {
provider,
coordinator: "configured-broker",
brokerMode: "managed",
brokerAuth: "configured",
...overrides,
};
}
function directBrokerConfig(provider: string): Record<string, unknown> {
return { provider, coordinator: "", brokerMode: "", brokerAuth: "missing" };
}
function windowsHydrateArgs(...args: string[]): string[] {
return ["actions", "hydrate", "--provider", "aws", "--target", "windows", ...args];
}
function runSuccessfulWindowsHydrate(...args: string[]): ParsedWrapperRun {
return runSuccessfulWrapper(azureProviderHelp, windowsHydrateArgs(...args));
}
function normalizeShellLineEndings(value: string): string {
return value.replace(/\r\n/g, "\n");
}
async function waitForCondition(
predicate: () => boolean,
timeoutMs = 8_000,
now = Date.now,
): Promise<void> {
const started = now();
while (now() - started < timeoutMs) {
if (predicate()) {
return;
}
await delay(10);
}
throw new Error("timed out waiting for condition");
}
async function waitForProcessClose(
child: ChildProcess,
timeoutMs = 12_000,
): Promise<{ status: number | null; signal: NodeJS.Signals | null }> {
return await Promise.race([
new Promise<{ status: number | null; signal: NodeJS.Signals | null }>((resolve, reject) => {
child.once("error", reject);
child.once("close", (status, signal) => resolve({ status, signal }));
}),
delay(timeoutMs, undefined, { ref: false }).then(() => {
throw new Error("timed out waiting for wrapper process close");
}),
]);
}
type WrapperCleanupProof =
| { kind: "signal"; entrypoint: "node" | "pnpm"; repeated: boolean; cooperative?: boolean }
| { kind: "readiness" }
| { kind: "preparation"; cleanupFails?: boolean }
| { kind: "stdin"; target: "macos" | "capsule" }
| { kind: "escaped" }
| { kind: "removal"; target: "script" | "source"; exitCode: 0 | 23 };
type WrapperFixtureIdentity = { pid: number; parentPid: number; cwd: string };
type WrapperReadinessPhase = { phase: string; at: number; pid?: number; parentPid?: number };
function expectMirrorDirectories(syncRoot: string, repository: string, allocation?: string) {
const key = createHash("sha256").update(realpathSync(repository)).digest("hex");
const mirrors = path.join(syncRoot, "mirrors");
expect(readdirSync(mirrors).toSorted()).toEqual([".allocation.lock", key].toSorted());
const slot = path.join(mirrors, key);
expect(readdirSync(slot).toSorted()).toEqual(["lock", "stage"]);
for (const file of [path.join(mirrors, ".allocation.lock"), path.join(slot, "lock")]) {
const stat = lstatSync(file);
expect(stat.isFile()).toBe(true);
expect(stat.nlink).toBe(1);
expect(stat.size).toBe(0);
const lock = new DatabaseSync(file, { timeout: 0 });
try {
lock.exec("PRAGMA journal_mode=MEMORY; BEGIN EXCLUSIVE; ROLLBACK");
} finally {
lock.close();
}
}
const id = readFileSync(path.join(slot, "stage"), "utf8").trim();
expect(id).toMatch(/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/u);
if (allocation) {
expect(path.basename(allocation)).toBe(`openclaw-crabbox-sync-${id}`);
}
const cursorName = "openclaw-crabbox-sync-discovery";
const cursor = path.join(syncRoot, cursorName);
if (existsSync(cursor)) {
expect(readdirSync(cursor)).toEqual(["position.json"]);
const identity = (directory: string) => {
const stat = lstatSync(directory, { bigint: true });
return { dev: String(stat.dev), ino: String(stat.ino) };
};
expect(JSON.parse(readFileSync(path.join(cursor, "position.json"), "utf8"))).toMatchObject({
version: 1,
rootIdentity: identity(syncRoot),
cursorIdentity: identity(cursor),
});
}
expect(readdirSync(syncRoot).toSorted()).toEqual(
[
"mirrors",
...(allocation ? [path.basename(allocation)] : []),
...(existsSync(cursor) ? [cursorName] : []),
].toSorted(),
);
return { key, id };
}
function expectIdleSourceMirror(source: string, repository: string, syncRoot: string) {
const allocation = path.dirname(path.dirname(source));
const { key, id } = expectMirrorDirectories(syncRoot, repository, allocation);
expect(lstatSync(source).isDirectory()).toBe(true);
const receipt = JSON.parse(readFileSync(path.join(allocation, "staging.json"), "utf8"));
expect(receipt).toMatchObject({
version: 2,
id,
repository: realpathSync(repository),
kind: "capsule",
durable: true,
users: "settled",
state: "preserved",
mirror: { key, idle: true },
});
expect(receipt.hold).toBeUndefined();
expect(receipt.manifest).toBe(
createHash("sha256")
.update(readFileSync(path.join(allocation, "manifest.json")))
.digest("hex"),
);
expect(receipt.mirror.database).toBe(
createHash("sha256")
.update(JSON.stringify(receipt.witness ?? null) + "\0")
.update(readFileSync(path.join(allocation, "mirror.sqlite")))
.digest("hex"),
);
}
async function runWrapperCleanupProof(proof: WrapperCleanupProof): Promise<void> {
const entrypoint = proof.kind === "signal" ? proof.entrypoint : "node";
const cooperative = proof.kind === "signal" && proof.cooperative;
const scriptRemoval = proof.kind === "removal" && proof.target === "script";
const preparationCleanupFailure = proof.kind === "preparation" && proof.cleanupFails;
const readsStdin = proof.kind === "stdin" || scriptRemoval;
const blacksmith = !readsStdin;
const hasDescendant =
proof.kind === "signal" || proof.kind === "escaped" || proof.kind === "readiness";
await withShimFixture(
"scripts/crabbox-wrapper.mjs",
async ({ checkoutRoot: producer, fixtureRoot, implementationPath, wrapperPath }) => {
copyFileSync(realBundledWrapperPath, implementationPath);
const syncRoot = path.join(fixtureRoot, "sync");
const scriptTmpRoot = path.join(fixtureRoot, "tmp");
mkdirSync(scriptTmpRoot);
const home = path.join(fixtureRoot, "home");
const stateRoot = path.join(home, ".local", "state");
const claimPath = path.join(stateRoot, "crabbox", "claims", "tbx_fixture.json");
const descendantPidPath = path.join(fixtureRoot, "descendant.pid");
const identityPath = path.join(fixtureRoot, "run.json");
const preparationPath = path.join(fixtureRoot, "preparation.json");
const stdinReadyPath = path.join(fixtureRoot, "stdin.ready");
const runSpawnedPath = path.join(fixtureRoot, "run.spawned");
const removalFailurePath = path.join(fixtureRoot, "removal.json");
const releasePath = path.join(fixtureRoot, "escaped.release");
const wrapperExitPath = path.join(fixtureRoot, "wrapper-exit.json");
const terminalCommandPidPath = path.join(fixtureRoot, "terminal-command.pid");
const phasesPath = path.join(fixtureRoot, "readiness-phases.json");
const ownerPreload = path.join(fixtureRoot, "owner.cjs");
writeFileSync(
ownerPreload,
`
const fs = require("node:fs");
const path = require("node:path");
const entry = path.resolve(process.argv[1] || ".");
const phasesPath = ${JSON.stringify(phasesPath)};
const phases = fs.existsSync(phasesPath) ? JSON.parse(fs.readFileSync(phasesPath, "utf8")) : [];
const phase = (name) => {
phases.push({ phase: name, at: Date.now(), pid: process.pid, parentPid: process.ppid });
const temporary = phasesPath + "." + process.pid;
fs.writeFileSync(temporary, JSON.stringify(phases));
fs.renameSync(temporary, phasesPath);
};
if (entry === ${JSON.stringify(wrapperPath)}) phase("entrypoint started");
if (entry === ${JSON.stringify(implementationPath)}) {
phase("loading wrapper");
// Stall at phase publication; teardown must already own this PID.
if (${proof.kind === "readiness"}) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0);
process.once("exit", (code) => fs.writeFileSync(${JSON.stringify(wrapperExitPath)}, JSON.stringify({ code })));
const childProcess = require("node:child_process");
const spawn = childProcess.spawn;
childProcess.spawn = (command, args, options) => {
if (args?.[0] === "--version") phase("wrapper loaded; probing Crabbox version");
if (args?.[0] === "run" && args[1] !== "--help") {
phase("starting Crabbox run");
fs.writeFileSync(${JSON.stringify(runSpawnedPath)}, "spawned");
}
return spawn(command, args, options);
};
const spawnSync = childProcess.spawnSync;
childProcess.spawnSync = (command, args, options) => {
if (args?.[0] === "sync-plan") phase("preparing source capsule");
return spawnSync(command, args, options);
};
if (${proof.kind === "stdin"}) {
const readFile = fs.readFileSync;
fs.readFileSync = (input, ...args) => {
if (input === 0) fs.writeFileSync(${JSON.stringify(stdinReadyPath)}, "ready");
return readFile(input, ...args);
};
const consumers = require("node:stream/consumers");
const consume = consumers.buffer;
consumers.buffer = (input) => {
const result = consume(input);
if (input === process.stdin) fs.writeFileSync(${JSON.stringify(stdinReadyPath)}, "ready");
return result;
};
}
if (${proof.kind === "removal" || preparationCleanupFailure}) {
const remove = fs.rmSync;
fs.rmSync = (file, ...args) => {
const target = path.resolve(String(file));
const selected = ${Boolean(preparationCleanupFailure)}
? fs.existsSync(${JSON.stringify(preparationPath)}) && target === path.dirname(JSON.parse(fs.readFileSync(${JSON.stringify(preparationPath)}, "utf8")).cwd)
: ${scriptRemoval}
? path.dirname(target) === ${JSON.stringify(scriptTmpRoot)} && path.basename(target).startsWith("openclaw-crabbox-source-script-")
: (path.basename(target) === "payload" && path.dirname(path.dirname(target)) === ${JSON.stringify(syncRoot)} && path.basename(path.dirname(target)).startsWith("openclaw-crabbox-sync-")) || (path.dirname(target) === ${JSON.stringify(syncRoot)} && path.basename(target).startsWith("openclaw-crabbox-sync-"));
if (selected) {
fs.writeFileSync(${JSON.stringify(removalFailurePath)}, JSON.stringify({ path: target }));
throw Object.assign(new Error("fixture removal denied"), { code: "EACCES", path: target });
}
return remove(file, ...args);
};
}
require("node:module").syncBuiltinESMExports();
}
`,
);
const capturePath = ".crabbox/captures/signal.txt";
const captureBytes = Buffer.from("cancellation diagnostic\n");
const nodeExecPath = resolveTestNodeExecPath();
const env = {
...process.env,
...testHomeEnv(home),
XDG_STATE_HOME: stateRoot,
TMPDIR: scriptTmpRoot,
TMP: scriptTmpRoot,
TEMP: scriptTmpRoot,
// Reuse prepared package-manager binaries while keeping user state isolated.
COREPACK_HOME:
process.env.COREPACK_HOME ||
path.join(
process.env.XDG_CACHE_HOME || path.join(homedir(), ".cache"),
"node",
"corepack",
),
NODE_OPTIONS: `${process.env.NODE_OPTIONS || ""} --require ${JSON.stringify(ownerPreload)}`,
PATH: [
makeFakeCrabbox(defaultProviderHelp),
path.dirname(nodeExecPath),
process.env.PATH ?? "",
].join(path.delimiter),
GIT_CONFIG_GLOBAL: "/dev/null",
GIT_CONFIG_NOSYSTEM: "1",
GIT_AUTHOR_NAME: "Signal fixture",
GIT_AUTHOR_EMAIL: "fixture@example.invalid",
GIT_COMMITTER_NAME: "Signal fixture",
GIT_COMMITTER_EMAIL: "fixture@example.invalid",
CRABBOX_PROVIDER: "",
PNPM_CONFIG_MODULES_DIR: path.join(repoRoot, "node_modules"),
OPENCLAW_CRABBOX_WRAPPER_IGNORE_REPO_BINARY: "1",
OPENCLAW_CRABBOX_SYNC_TMPDIR: syncRoot,
OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES: "0",
OPENCLAW_FAKE_CRABBOX_CLAIM_PATH: blacksmith ? claimPath : "",
OPENCLAW_FAKE_CRABBOX_TIMING_LEASE_ID: blacksmith ? "tbx_fixture" : "",
OPENCLAW_FAKE_CRABBOX_DESCENDANT_PID_PATH: hasDescendant ? descendantPidPath : "",
OPENCLAW_FAKE_CRABBOX_RUN_IDENTITY_PATH: identityPath,
OPENCLAW_FAKE_CRABBOX_SYNC_PLAN_READY_PATH:
proof.kind === "preparation" ? preparationPath : "",
OPENCLAW_FAKE_CRABBOX_ESCAPED_RELEASE_PATH: proof.kind === "escaped" ? releasePath : "",
OPENCLAW_FAKE_CRABBOX_DELAYED_ARTIFACT: cooperative ? ".crabbox/captures/shutdown.txt" : "",
OPENCLAW_FAKE_CRABBOX_RUN_STATUS: proof.kind === "removal" ? String(proof.exitCode) : "0",
...(proof.kind === "escaped"
? { VITEST: "true", OPENCLAW_TEST_CRABBOX_CHILD_KILL_GRACE_MS: "100" }
: {}),
OPENCLAW_FAKE_CRABBOX_ARTIFACTS: JSON.stringify({
[capturePath]: captureBytes.toString("base64"),
// Unknown native state requires full disposal after artifact preservation.
...(proof.kind === "removal" && proof.target === "source"
? {
".crabbox/state/removal-proof":
Buffer.from("discard this mirror\n").toString("base64"),
}
: {}),
}),
};
const git = (...args: string[]) => {
const result = spawnSync("git", args, { cwd: producer, env, encoding: "utf8" });
expect(result.status, result.stderr).toBe(0);
return result.stdout;
};
writeFileSync(
path.join(producer, "package.json"),
JSON.stringify({
type: "module",
packageManager: JSON.parse(readFileSync(path.join(repoRoot, "package.json"), "utf8"))
.packageManager,
scripts: { "crabbox:run": "node scripts/crabbox-wrapper.mjs run" },
}),
);
writeFileSync(
path.join(producer, ".gitignore"),
"scripts/\nnode_modules/\npackage.json\npnpm-lock.yaml\n.crabbox/\n",
);
writeFileSync(path.join(producer, "fixture.txt"), "original source\n");
git("init", "-q", "-b", "main");
git("add", ".gitignore", "fixture.txt");
git("commit", "-qm", "fixture");
git("remote", "add", "origin", producer);
git("update-ref", "refs/remotes/origin/main", "HEAD");
const sourceIndex = git("ls-files", "--stage", "-z");
const args = blacksmith
? ["--provider", "blacksmith-testbox", "--keep", "--timing-json", "--", "echo", "ok"]
: [
"--provider",
"aws",
"--target",
proof.kind === "stdin" && proof.target === "capsule" ? "linux" : "macos",
"--script-stdin",
...(scriptRemoval || (proof.kind === "stdin" && proof.target === "capsule")
? ["--", "node", "scripts/check-changed.mjs"]
: []),
];
let output = "";
let stop: (() => void) | undefined;
let forceStop: (() => void) | undefined;
let entrypointClosed: Promise<unknown> | undefined;
let finishTerminal: (() => Promise<void>) | undefined;
let entrypointPid = 0;
let pnpmRetainsShell = false;
const pnpmOwnerChain: Array<{ pid: number; parentPid: number; command: string }> = [];
let identity: WrapperFixtureIdentity | undefined;
let preparationIdentity: WrapperFixtureIdentity | undefined;
let descendantPid = 0;
let failure: Error | undefined;
let readinessFailure: Error | undefined;
const readinessTimeoutMs = 8_000;
const waitForReadiness = async (file: string) => {
try {
let deliberatelyStalled = false;
await waitForCondition(
() => {
if (proof.kind === "readiness") {
const phases: WrapperReadinessPhase[] = JSON.parse(
readFileSync(phasesPath, "utf8"),
);
deliberatelyStalled = phases.some(({ phase }) => phase === "loading wrapper");
}
return existsSync(file);
},
readinessTimeoutMs,
// Startup stays real; expire only after the fixture publishes its deliberate hang.
() => Date.now() + (deliberatelyStalled ? readinessTimeoutMs : 0),
);
} catch (cause) {
const phases: WrapperReadinessPhase[] = JSON.parse(readFileSync(phasesPath, "utf8"));
const startedAt = phases[0]!.at;
readinessFailure = new Error(
`wrapper readiness not observed within ${readinessTimeoutMs / 1000} s; last observed phase ${phases.at(-1)!.phase}\n${phases.map(({ phase, at }) => `+${at - startedAt} ms: ${phase}`).join("\n")}\n${output}`,
{ cause },
);
throw readinessFailure;
}
};
writeFileSync(
phasesPath,
JSON.stringify([{ phase: "entrypoint spawn requested", at: Date.now() }]),
);
try {
let exited: Promise<{ status: number | null; signal: NodeJS.Signals | null }>;
if (entrypoint === "pnpm") {
const command = resolvePnpmRunner({
cwd: producer,
env,
pnpmArgs: ["crabbox:run", "--", ...args],
});
const terminalOwnerPath = path.join(fixtureRoot, "terminal.cjs");
writeFileSync(
terminalOwnerPath,
`
const fs = require("node:fs");
const { spawn } = require("node:child_process");
process.on("SIGINT", () => {});
setInterval(() => {}, 1000);
const child = spawn(${JSON.stringify(command.command)}, ${JSON.stringify(command.args)}, { stdio: "inherit" });
if (child.pid) {
fs.writeFileSync(${JSON.stringify(terminalCommandPidPath + ".tmp")}, String(child.pid));
fs.renameSync(${JSON.stringify(terminalCommandPidPath + ".tmp")}, ${JSON.stringify(terminalCommandPidPath)});
}
child.once("error", (error) => { console.error(error); process.exit(1); });
child.once("exit", (code, signal) => {
process.stdout.write("\\nopenclaw-pnpm-exit:" + JSON.stringify({ status: code, signal }) + "\\n");
});
`,
);
// A terminal session outlives its foreground command; PNPM exit must not hang up cleanup.
const terminal = await spawnTerminalPty({
file: nodeExecPath,
args: [terminalOwnerPath],
cwd: producer,
env: Object.fromEntries(
Object.entries(env).filter(
(entry): entry is [string, string] => entry[1] !== undefined,
),
),
cols: 80,
rows: 24,
});
entrypointPid = terminal.pid;
let reportExit!: (result: {
status: number | null;
signal: NodeJS.Signals | null;
}) => void;
exited = new Promise((resolve) => {
reportExit = resolve;
});
terminal.onData((data) => {
output += data;
const receipt = /\r?\nopenclaw-pnpm-exit:(\{[^\r\n]+\})\r?\n/u.exec(output);
if (receipt?.[1]) {
output = output.replace(receipt[0], "");
reportExit(JSON.parse(receipt[1]));
}
});
entrypointClosed = new Promise<void>((resolve) => {
terminal.onExit(({ exitCode, signal }) => {
reportExit({ status: signal ? 128 + signal : exitCode, signal: null });
resolve();
});
});
stop = () => terminal.write("\x03");
forceStop = () => terminal.kill("SIGKILL");
finishTerminal = async () => {
forceStop!();
await entrypointClosed;
};
} else {
const runner = spawn(process.execPath, [wrapperPath, "run", ...args], {
cwd: producer,
env,
stdio: ["pipe", "pipe", "pipe"],
});
entrypointPid = runner.pid ?? 0;
runner.stdout.on("data", (data) => {
output += data;
});
runner.stderr.on("data", (data) => {
output += data;
});
exited = waitForProcessClose(runner, 20_000);
entrypointClosed = exited;
stop = () => {
runner.kill(proof.kind === "stdin" ? "SIGINT" : "SIGTERM");
};
forceStop = () => {
runner.kill("SIGKILL");
};
if (scriptRemoval) {
runner.stdin.end("#!/usr/bin/env bash\nprintf 'script input\\n'\n");
}
}
if (proof.kind === "preparation") {
await waitForReadiness(preparationPath);
preparationIdentity = JSON.parse(readFileSync(preparationPath, "utf8"));
expect(existsSync(path.join(preparationIdentity!.cwd, "fixture.txt"))).toBe(true);
stop();
} else if (proof.kind === "stdin") {
await waitForReadiness(stdinReadyPath);
if (proof.target === "capsule") {
expect(readdirSync(syncRoot)).toHaveLength(1);
}
stop();
} else if (hasDescendant) {
await waitForReadiness(descendantPidPath);
descendantPid = Number.parseInt(readFileSync(descendantPidPath, "utf8"), 10);
identity = JSON.parse(readFileSync(identityPath, "utf8"));
expect(isProcessAlive(descendantPid)).toBe(true);
expect(JSON.parse(readFileSync(claimPath, "utf8")).repoRoot).toBe(identity!.cwd);
expect(existsSync(identity!.cwd)).toBe(true);
if (proof.kind === "signal") {
if (entrypoint === "pnpm") {
const phases: WrapperReadinessPhase[] = JSON.parse(readFileSync(phasesPath, "utf8"));
const shim = phases.find(({ phase }) => phase === "entrypoint started")!;
const pnpmPid = Number(readFileSync(terminalCommandPidPath, "utf8"));
expect(shim.parentPid).toBeGreaterThan(1);
expect(pnpmPid).toBeGreaterThan(1);
let ownerPid = shim.parentPid;
const visited = new Set<number>();
while (ownerPid !== pnpmPid) {
if (
ownerPid === undefined ||
!Number.isSafeInteger(ownerPid) ||
ownerPid <= 1 ||
visited.has(ownerPid)
) {
throw new Error(
`Wrapper shim is not owned by pnpm ${pnpmPid}: ${JSON.stringify(pnpmOwnerChain)}`,
);
}
visited.add(ownerPid);
const owner = spawnSync(
"ps",
["-o", "ppid=", "-o", "comm=", "-p", String(ownerPid)],
{ encoding: "utf8" },
);
expect(owner.status, owner.stderr).toBe(0);
const [parentPid, ...command] = owner.stdout.trim().split(/\s+/u);
const commandName = command.join(" ");
pnpmOwnerChain.push({
pid: ownerPid,
parentPid: Number(parentPid),
command: commandName,
});
pnpmRetainsShell ||= ["sh", "dash", "bash"].includes(path.basename(commandName));
ownerPid = Number(parentPid);
}
}
stop();
if (proof.repeated) {
await delay(20);
stop();
}
}
}
const result = await exited;
if (cooperative) {
expect(isProcessAlive(entrypointPid), "terminal session still owns cleanup output").toBe(
true,
);
}
const expectedStatus =
proof.kind === "escaped"
? 1
: proof.kind === "removal"
? proof.exitCode || 1
: proof.kind === "stdin" || entrypoint === "pnpm"
? 130
: 143;
const phases: WrapperReadinessPhase[] = JSON.parse(readFileSync(phasesPath, "utf8"));
const wrapperPid = phases.find(({ phase }) => phase === "loading wrapper")!.pid!;
// A pnpm interruption status is not the implementation's cleanup receipt.
await waitForCondition(() => !isProcessAlive(wrapperPid), 12_000);
expect(JSON.parse(readFileSync(wrapperExitPath, "utf8")), output).toEqual({
code: expectedStatus,
});
const retained = path.join(producer, ".crabbox", "wrapper-artifacts");
if (proof.kind === "preparation" || proof.kind === "stdin") {
if (proof.kind === "stdin" && proof.target === "capsule") {
expect(output).toContain("syncing from temporary full checkout");
}
expect(existsSync(runSpawnedPath), output).toBe(false);
expect(existsSync(identityPath)).toBe(false);
expect(existsSync(claimPath)).toBe(false);
if (preparationCleanupFailure) {
const payload = path.dirname(preparationIdentity!.cwd);
const allocation = path.dirname(payload);
expect(JSON.parse(readFileSync(removalFailurePath, "utf8"))).toEqual({
path: payload,
});
expectMirrorDirectories(syncRoot, producer, allocation);
expect(readFileSync(path.join(preparationIdentity!.cwd, "fixture.txt"), "utf8")).toBe(
"original source\n",
);
expect(output).toContain("fixture removal denied");
expect(output).toContain(allocation);
} else if (blacksmith && existsSync(syncRoot)) {
expectMirrorDirectories(syncRoot, producer);
} else {
expect(existsSync(syncRoot) ? readdirSync(syncRoot) : []).toEqual([]);
}
expect(
readdirSync(scriptTmpRoot).filter((name) => name.startsWith("openclaw-crabbox-")),
).toEqual([]);
if (preparationIdentity) {
expect(isProcessAlive(preparationIdentity.pid)).toBe(false);
}
} else if (proof.kind === "escaped") {
expect(output).toContain("child cleanup failed");
expect(output).toContain("child cleanup is unverified; temporary source retained");
expect(output).toContain(identity!.cwd);
expect(isProcessAlive(descendantPid)).toBe(true);
expect(JSON.parse(readFileSync(claimPath, "utf8")).repoRoot).toBe(identity!.cwd);
expect(readFileSync(path.join(identity!.cwd, capturePath))).toEqual(captureBytes);
expect(existsSync(retained)).toBe(false);
writeFileSync(releasePath, "release");
await waitForCondition(() => !isProcessAlive(descendantPid));
expect(readFileSync(releasePath + ".read", "utf8")).toBe("original source\n");
} else {
identity ??= JSON.parse(readFileSync(identityPath, "utf8"));
expect(isProcessAlive(identity!.pid), output).toBe(false);
if (hasDescendant) {
expect(isProcessAlive(descendantPid), output).toBe(false);
}
if (blacksmith) {
expect(JSON.parse(readFileSync(claimPath, "utf8")).repoRoot, output).toBe(producer);
}
const invocations = readdirSync(retained);
expect(invocations).toHaveLength(1);
expect(
readFileSync(path.join(retained, invocations[0]!, "captures", "signal.txt")),
).toEqual(captureBytes);
if (cooperative) {
expect(
readFileSync(
path.join(retained, invocations[0]!, "captures", "shutdown.txt"),
"utf8",
),
output,
).toBe("graceful shutdown\n");
}
if (proof.kind === "removal") {
const failedPath = (
JSON.parse(readFileSync(removalFailurePath, "utf8")) as { path: string }
).path;
expect(existsSync(failedPath)).toBe(true);
expect(output).toContain("fixture removal denied");
if (proof.target === "source") {
expect(output).toContain(`temporary checkout cleanup failed at ${identity!.cwd}`);
expectMirrorDirectories(syncRoot, producer, path.dirname(failedPath));
expect(readFileSync(path.join(identity!.cwd, "fixture.txt"), "utf8")).toBe(
"original source\n",
);
} else {
expect(output).toContain("remote script cleanup failed");
expect(
readdirSync(scriptTmpRoot).filter((name) => name.startsWith("openclaw-crabbox-")),
).toEqual([path.basename(failedPath)]);
expect(readdirSync(syncRoot)).toEqual([]);
}
} else {
expectIdleSourceMirror(identity!.cwd, producer, syncRoot);
expect(readFileSync(path.join(identity!.cwd, "fixture.txt"), "utf8")).toBe(
"original source\n",
);
expect(existsSync(path.join(identity!.cwd, ".crabbox"))).toBe(false);
}
}
expect(readFileSync(path.join(producer, "fixture.txt"), "utf8")).toBe("original source\n");
expect(git("ls-files", "--stage", "-z")).toBe(sourceIndex);
// pnpm 12.4.2 escalates the second interrupt to TERM on its immediate child.
// Corepack can translate a retained shell's TERM into a numeric exit code.
if (entrypoint === "pnpm") {
const exitStatus = result.signal
? 128 + osConstants.signals[result.signal]
: result.status;
const expectedLauncherStatus =
proof.kind === "signal" && proof.repeated && pnpmRetainsShell ? 143 : expectedStatus;
expect(exitStatus, `${output}\npnpm ownership: ${JSON.stringify(pnpmOwnerChain)}`).toBe(
expectedLauncherStatus,
);
} else {
expect(result, output).toEqual({ status: expectedStatus, signal: null });
}
if (finishTerminal) {
await finishTerminal();
}
} catch (error) {
failure =
error instanceof Error ? error : new Error("signal proof failed", { cause: error });
}
try {
// Release even a late-starting escaped leaf before any fixture disposal.
if (proof.kind === "escaped") {
writeFileSync(releasePath, "release");
}
if (entrypointPid !== 0 && (!Number.isSafeInteger(entrypointPid) || entrypointPid <= 1)) {
throw new Error("invalid fixture entrypoint PID");
}
if (entrypointPid && isProcessAlive(entrypointPid)) {
try {
forceStop?.();
} catch (error) {
if (isProcessAlive(entrypointPid)) {
throw error;
}
}
}
const phases: WrapperReadinessPhase[] = JSON.parse(readFileSync(phasesPath, "utf8"));
const wrapperPid = phases.find(({ phase }) => phase === "loading wrapper")?.pid ?? 0;
identity ??= existsSync(identityPath)
? JSON.parse(readFileSync(identityPath, "utf8"))
: undefined;
preparationIdentity ??= existsSync(preparationPath)
? JSON.parse(readFileSync(preparationPath, "utf8"))
: undefined;
descendantPid ||= existsSync(descendantPidPath)
? Number.parseInt(readFileSync(descendantPidPath, "utf8"), 10)
: 0;
const terminalCommandPid = existsSync(terminalCommandPidPath)
? Number.parseInt(readFileSync(terminalCommandPidPath, "utf8"), 10)
: 0;
const ownedPids = new Set([
// The atomic phase record owns its PID even before later readiness receipts.
...phases.map(({ pid }) => pid),
entrypointPid,
terminalCommandPid,
identity?.pid,
preparationIdentity?.pid,
descendantPid,
]);
for (const pid of ownedPids) {
if (pid === undefined || pid === 0) {
continue;
}
if (!Number.isSafeInteger(pid) || pid <= 1) {
throw new Error("invalid owned fixture PID");
}
if (isProcessAlive(pid)) {
try {
process.kill(-pid, "SIGKILL");
} catch {
try {
process.kill(pid, "SIGKILL");
} catch (error) {
if (!(error instanceof Error && "code" in error && error.code === "ESRCH")) {
throw error;
}
}
}
}
}
await entrypointClosed;
await waitForCondition(() => [...ownedPids].every((pid) => !pid || !isProcessAlive(pid)));
const runReceiptRequired =
hasDescendant || proof.kind === "removal" || existsSync(runSpawnedPath);
if (
failure !== undefined &&
readinessFailure === undefined &&
(!wrapperPid ||
(entrypoint === "pnpm" && !terminalCommandPid) ||
(runReceiptRequired && !identity) ||
(hasDescendant && !descendantPid) ||
(proof.kind === "preparation" && !preparationIdentity) ||
(proof.kind === "stdin" && !existsSync(stdinReadyPath)))
) {
throw new Error("fixture ownership receipts are incomplete");
}
} catch (cause) {
throw Object.assign(
new AggregateError(
failure === undefined ? [cause] : [failure, cause],
readinessFailure
? `${readinessFailure.message}\nfixture teardown could not be verified`
: "fixture writers did not settle",
{ cause: failure ?? cause },
),
{ processTreeState: "indeterminate" },
);
}
if (failure !== undefined) {
// A startup timeout cannot certify writers whose ownership receipts never arrived.
if (readinessFailure) {
Object.assign(failure, { processTreeState: "indeterminate" });
}
throw failure;
}
},
);
}
function testCrabboxConfigDir(home: string): string {
if (process.platform === "darwin") {
return path.join(home, "Library", "Application Support", "crabbox");
}
if (process.platform === "win32") {
return path.join(home, "AppData", "Roaming", "crabbox");
}
return path.join(home, ".config", "crabbox");
}
function testHomeEnv(home: string): Record<string, string> {
return {
APPDATA: path.join(home, "AppData", "Roaming"),
HOME: home,
USERPROFILE: home,
XDG_CONFIG_HOME: path.join(home, ".config"),
XDG_STATE_HOME: "",
};
}
function expectGroupedShellCommand(remoteCommand: string, command: string): void {
expect(remoteCommand).toContain("&& { ");
expect(remoteCommand).toContain(command);
if (process.platform !== "win32") {
expect(remoteCommand).toContain(`${command}\n}`);
}
}
function expectMacosJsBootstrap(remoteCommand: string, command: string): void {
expect(remoteCommand).toContain("openclaw_crabbox_bootstrap_macos_js");
expectGroupedShellCommand(remoteCommand, command);
}
function expectMacosPackageCommand(
{ output, remoteCommand }: ParsedWrapperRun,
command: string,
beforeGrouped?: (remoteCommand: string) => void,
): void {
expect(output.args).toContain("--shell");
expect(remoteCommand).toContain("openclaw_crabbox_bootstrap_macos_js");
expect(remoteCommand).toContain("pnpm --version >&2");
expect(remoteCommand).toContain("openclaw_crabbox_require_macos_swift_63");
beforeGrouped?.(remoteCommand);
expectGroupedShellCommand(remoteCommand, command);
}
function runSuccessfulMacosShell(shellScript: string, options: WrapperOptions = {}) {
return runSuccessfulDefaultWrapper(
["run", "--provider", "aws", "--target", "macos", "--shell", "--", shellScript],
options,
);
}
function runSuccessfulMacosCommand(command: string[], options: WrapperOptions = {}) {
return runSuccessfulDefaultWrapper(
["run", "--provider", "aws", "--target", "macos", "--", ...command],
options,
);
}
function runSuccessfulMacosScript(script: string, trailingArgs: string[] = []): ParsedWrapperRun {
return runSuccessfulDefaultWrapper(
["run", "--provider", "aws", "--target", "macos", "--script-stdin", ...trailingArgs],
{ input: script },
);
}
function runDelegatedBlacksmith(args: string[], env: Record<string, string>) {
if (process.platform === "win32") {
return runDefaultWrapper(args, { ...cleanSparseSyncOptions, env });
}
const physicalSyncRoot = mkdtempSync(path.join(tmpdir(), "openclaw-crabbox-sync-physical-"));
const syncRootAlias = `${physicalSyncRoot}-alias`;
symlinkSync(physicalSyncRoot, syncRootAlias, "dir");
tempDirs.push(syncRootAlias, physicalSyncRoot);
const result = runDefaultWrapper(args, {
...cleanSparseSyncOptions,
env: { ...env, OPENCLAW_CRABBOX_SYNC_TMPDIR: syncRootAlias },
});
if (result.stdout.trim()) {
expect(
parseFakeCrabboxOutput(result).cwd.startsWith(`${realpathSync(physicalSyncRoot)}${path.sep}`),
).toBe(true);
}
return result;
}
const remoteChangedGateEnvPrefix =
"OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 CI=1";
const remoteChangedGateExport = `export ${remoteChangedGateEnvPrefix};`;
const remoteChangedGateFetch = "refs/remotes/origin/main";
const sparseChangedGateOptions = {
gitResponses: {
[GIT_CONFIG_SPARSE_KEY]: { stdout: "true\n" },
[GIT_STATUS_PORCELAIN_KEY]: { stdout: "" },
[GIT_MERGE_BASE_MAIN_HEAD_KEY]: { stdout: "abc123\n" },
},
} satisfies WrapperOptions;
const cleanSparseSyncOptions = {
gitResponses: {
[GIT_CONFIG_SPARSE_KEY]: { stdout: "true\n" },
[GIT_STATUS_PORCELAIN_KEY]: { stdout: "" },
},
} satisfies WrapperOptions;
function withSparseSyncRoot(
name: string,
env: Record<string, string>,
check: (fixture: { result: ReturnType<typeof runWrapper>; syncRoot: string }) => void,
): void {
const syncRoot = path.join(artifactTempDirs.make("openclaw-crabbox-sparse-sync-"), name);
const result = runDefaultWrapper(["run", "--provider", "aws", "--", "echo ok"], {
...cleanSparseSyncOptions,
env: { ...env, OPENCLAW_CRABBOX_SYNC_TMPDIR: syncRoot },
});
check({ result, syncRoot });
}
function runSparseShell(shellScript: string) {
return runSuccessfulDefaultWrapper(
["run", "--provider", "aws", "--shell", "--", shellScript],
sparseChangedGateOptions,
);
}
function expectChangedGateGitBootstrap(remoteCommand: string): void {
expect(remoteCommand).toContain("node -e");
expect(remoteCommand).toContain(".openclaw-crabbox-changed-gate.bundle");
expect(remoteCommand).toContain('"baseSha":"abc123"');
expect(remoteCommand).not.toMatch(/git (?:reset|clean|restore|commit) /u);
expect(remoteCommand).not.toContain("remote-testbox-sync");
expect(remoteCommand).not.toContain("; &&");
}
afterAll(() => {
rmSync(bundledWrapperPath, { force: true });
rmSync(realBundledWrapperPath, { force: true });
for (const dir of tempDirs.splice(0)) {
rmSync(dir, { recursive: true, force: true });
}
});
describe("scripts/crabbox-wrapper", () => {
beforeAll(async () => {
mkdirSync(path.dirname(bundledWrapperPath), { recursive: true });
const bundleOptions = {
bundle: true,
entryPoints: [path.join(repoRoot, "scripts/crabbox-wrapper.mts")],
format: "esm",
logLevel: "silent",
platform: "node",
target: "node22",
// Keep the Windows worker and native dependency resolution at their source owner.
// Relocating this module into a fixture would relocate its import.meta.url too.
plugins: [
{
name: "managed-child-source-owner",
setup(builder) {
builder.onResolve({ filter: /lib\/managed-child-process\.mts$/ }, (args) => ({
path: pathToFileURL(path.resolve(args.resolveDir, args.path)).href,
external: true,
}));
},
},
],
// esbuild needs Node's require for CommonJS SDK dependencies inside ESM output.
banner: {
js: 'import { createRequire as createBundleRequire } from "node:module"; const require = createBundleRequire(import.meta.url);',
},
} satisfies BuildOptions;
await build({
...bundleOptions,
outfile: realBundledWrapperPath,
});
bundledSetupPath = path.join(
makeTempDir(tempDirs, "openclaw-crabbox-setup-"),
"openclaw/scripts/crabbox-setup.mjs",
);
await build({
...bundleOptions,
entryPoints: [path.join(repoRoot, "scripts/crabbox-setup.mts")],
outfile: bundledSetupPath,
});
// Argument routing tests isolate source preparation; the real-Git fixture below
// executes the unmocked producer and generated receiver together.
const producerStub = path.join(
makeTempDir(tempDirs, "openclaw-source-owner-stub-"),
"producer.mjs",
);
writeFileSync(
producerStub,
String.raw`
import fs from "node:fs";
import path from "node:path";
export function prepareCrabboxSourceCapsule({syncRoot, base}) {
fs.mkdirSync(syncRoot, {recursive:true});
const directory = fs.mkdtempSync(path.join(syncRoot,"openclaw-crabbox-sync-"));
const bundlePath = ".openclaw-crabbox-changed-gate.bundle";
fs.writeFileSync(path.join(directory,bundlePath), "fixture capsule");
return {directory,bundlePath,staging:{admitted(){},settled(){},preserved(){},hold(){}},sourceSha:"d".repeat(40),baseSha:base === "origin/main" ? process.env.OPENCLAW_FAKE_GIT_BASE_SHA || "abc123" : base,tree:"e".repeat(40),carrier:"f".repeat(40),digest:"a".repeat(64),cleanup(){fs.rmSync(directory,{recursive:true,force:true});}};
}
`,
);
await build({
...bundleOptions,
plugins: [
...bundleOptions.plugins,
{
name: "source-capsule-fixture",
setup(builder) {
builder.onResolve({ filter: /crabbox-source-capsule\.mts$/ }, () => ({
path: producerStub,
}));
},
},
],
outfile: bundledWrapperPath,
});
});
it("prepares the supported executable for later workflow steps", () => {
const directory = invocationLogTempDirs.make("openclaw-crabbox-setup-path-");
const githubPath = path.join(directory, "github-path");
const invocationLog = makeInvocationLog();
const result = spawnSync(process.execPath, [bundledSetupPath], {
cwd: directory,
encoding: "utf8",
env: wrapperEnv(defaultProviderHelp, {
env: {
GITHUB_PATH: githubPath,
OPENCLAW_STATE_DIR: path.join(directory, "state"),
OPENCLAW_FAKE_CRABBOX_VERSION: "crabbox 0.56.0",
OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG: invocationLog,
},
}),
});
expect(result.status, result.stderr).toBe(0);
const binary = path.join(
makeFakeCrabbox(defaultProviderHelp),
process.platform === "win32" ? "crabbox.cmd" : "crabbox",
);
expect(JSON.parse(result.stdout)).toEqual({ binary, version: "0.56.0" });
expect(readFileSync(githubPath, "utf8")).toBe(`${path.dirname(binary)}\n`);
expect(readInvocations(invocationLog)).toEqual([["--version"]]);
expect(existsSync(path.join(directory, "state"))).toBe(false);
});
it("uses brokered cloud providers as the final CI fallback", () => {
const { output, result } = runSuccessfulBrokerWrapper(
["run", "--workload=ci-fast", "--", "echo ok"],
{
env: {
OPENCLAW_FAKE_CRABBOX_UNREADY_PROVIDERS: "blacksmith-testbox,daytona,azure",
},
},
);
expect(output.args).toContain("aws");
expect(result.stderr).toContain("selected=aws");
});
it("keeps the configured provider when no workload is requested", () => {
const { output, result } = runSuccessfulBrokerWrapper(["run", "--", "echo ok"], {
configJson: managedBrokerConfig("aws", { target: "linux", windowsMode: "normal" }),
});
expect(output.args).not.toContain("--provider");
expect(result.stderr).not.toContain("route workload=");
});
it("derives run option arity from the probed Crabbox help", () => {
const helpText = `${defaultProviderHelp}${[
"sandbox-session-timeout duration",
"sandbox-memory float",
"sandbox-retries int",
"sandbox-setting string",
"sandbox-attachment value",
]
.map((option) => ` -${option}\n`)
.join("")}`;
const { output } = runSuccessfulWrapper(helpText, [
"run",
"--sandbox-session-timeout",
"30s",
"--sandbox-memory",
"1.5",
"--sandbox-retries",
"2",
"--sandbox-setting",
"safe",
"--sandbox-attachment",
"name=proof",
"--provider",
"local-container",
"--",
"echo",
"ok",
]);
expect(output.args).toEqual([
"run",
"--sandbox-session-timeout",
"30s",
"--sandbox-memory",
"1.5",
"--sandbox-retries",
"2",
"--sandbox-setting",
"safe",
"--sandbox-attachment",
"name=proof",
"--provider",
"local-container",
"--",
"echo",
"ok",
]);
});
it("routes the provider-neutral changed gate without consuming its run option values", () => {
const { output, remoteCommand, result } = runSuccessfulBrokerWrapper(
[
"run",
"--workload",
"ci-fast",
"--idle-timeout",
"90m",
"--ttl",
"240m",
"--timing-json",
"--",
"env",
"OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1",
"OPENCLAW_CHANGED_LANES_RAW_SYNC=1",
"CI=1",
"PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN=false",
"corepack",
"pnpm",
"check:changed",
],
{ env: { OPENCLAW_FAKE_CRABBOX_UNREADY_PROVIDERS: "blacksmith-testbox" } },
);
expect(output.args).toContain("daytona");
expect(output.args).not.toContain("blacksmith-testbox");
expect(output.args).toContain("90m");
expect(output.args).toContain("240m");
expect(remoteCommand).toContain("corepack pnpm check:changed");
expect(result.stderr).toContain("route workload=ci-fast selected=daytona");
});
it("requires the originating provider when reusing a workload-routed lease", () => {
const result = runBrokerWrapper(
["run", "--workload", "interactive", "--id", "cbx_existing", "--", "echo ok"],
{},
);
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain(
"reusing a workload-routed lease with --id requires --provider",
);
});
it.each([
{
name: "explicit provider",
args: [
"run",
"--workload",
"untrusted",
"--provider",
"aws",
"--id",
"cbx_trusted",
"--",
"echo ok",
],
env: {},
},
{
name: "environment provider",
args: ["run", "--workload", "untrusted", "--id", "cbx_trusted", "--", "echo ok"],
env: { CRABBOX_PROVIDER: "aws" },
},
])("rejects untrusted lease reuse through an $name", ({ args, env }) => {
const result = runBrokerWrapper(args, {
env: {
...(env as Record<string, string>),
},
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain(
"workload=untrusted requires a fresh lease; --id reuse is forbidden",
);
});
it("reuses a workload-routed lease through its explicit provider", () => {
const { output, result } = runSuccessfulBrokerWrapper(
[
"run",
"--provider",
"daytona",
"--workload",
"interactive",
"--id",
"cbx_existing",
"--",
"echo ok",
],
{},
);
expect(output.args).toContain("daytona");
expect(result.stderr).not.toContain("route workload=");
});
it("routes configured macOS targets through AWS", () => {
const { output, result } = runSuccessfulBrokerWrapper(
["run", "--workload", "ci-proof", "--", "echo ok"],
{
configJson: managedBrokerConfig("blacksmith-testbox", {
target: "macos",
windowsMode: "normal",
}),
env: {
OPENCLAW_FAKE_CRABBOX_EXPECT_DOCTOR_TARGET: "macos",
},
},
);
expect(output.args).toContain("aws");
expect(result.stderr).toContain("chain=aws");
});
it("reuses the admitted version and runs one provider-scoped doctor per candidate", () => {
const invocationLog = makeInvocationLog();
const { output, result } = runSuccessfulBrokerWrapper(
["run", "--workload", "desktop", "--", "echo ok"],
{
env: {
OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG: invocationLog,
OPENCLAW_FAKE_CRABBOX_VERSION: "crabbox 0.56.0",
OPENCLAW_FAKE_CRABBOX_UNREADY_PROVIDERS: "azure",
OPENCLAW_FAKE_CRABBOX_WHOAMI_STATUS: "1",
},
},
);
expect(output.args).toContain("aws");
expect(result.stderr).toContain("selected=aws chain=azure,aws");
const invocations = readInvocations(invocationLog);
expect(invocations.filter(([command]) => command === "--version")).toEqual([["--version"]]);
expect(result.stderr).toContain("version=0.56.0");
expect(invocations.filter(([command]) => command === "doctor").map((args) => args[2])).toEqual([
"azure",
"aws",
]);
expect(invocations.filter(([command]) => command === "whoami")).toEqual([]);
});
it("falls through a doctor-reported auth failure to the next provider", () => {
const { output, result } = runSuccessfulBrokerWrapper(
["run", "--workload", "desktop", "--", "echo ok"],
{ env: { OPENCLAW_FAKE_CRABBOX_UNAUTHORIZED_PROVIDERS: "azure" } },
);
expect(output.args).toContain("aws");
expect(result.stderr).toContain("selected=aws chain=azure,aws");
expect(result.stderr).toContain("azure:doctor exited 1");
});
it("fails closed when provider readiness reports broker auth failure", () => {
const result = runBrokerWrapper(["run", "--provider", "aws", "--", "echo ok"], {
env: { OPENCLAW_FAKE_CRABBOX_PROVIDER_UNAUTHORIZED_PROVIDERS: "aws" },
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain(
"provider=aws requires managed Crabbox broker authentication for OpenClaw proof",
);
expect(result.stderr).toContain("login --url https://crabbox.openclaw.ai");
});
it("fails closed when doctor does not classify an auth failure", () => {
const result = runDefaultWrapper(["run", "--provider", "aws", "--", "echo ok"], {
env: {
OPENCLAW_FAKE_CRABBOX_UNCLASSIFIED_UNAUTHORIZED_PROVIDERS: "aws",
},
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("provider=aws failed readiness for OpenClaw proof");
expect(result.stderr).not.toContain("login --url");
});
it.each([
{ help: defaultProviderHelp, provider: "aws" },
{ help: azureProviderHelp, provider: "azure" },
])("trusts healthy doctor readiness for $provider", ({ help, provider }) => {
const invocationLog = makeInvocationLog();
const result = runWrapper(help, ["run", "--provider", provider, "--", "echo ok"], {
configStatus: 1,
env: {
OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG: invocationLog,
OPENCLAW_FAKE_CRABBOX_WHOAMI_STATUS: "1",
},
});
expect(result.status).toBe(0);
expect(readInvocations(invocationLog).filter(([command]) => command === "whoami")).toEqual([]);
});
it("keeps Blacksmith independent from broker auth probes", () => {
const invocationLog = makeInvocationLog();
const result = runDefaultWrapper(["run", "--provider", "blacksmith-testbox", "--", "echo ok"], {
configJson: directBrokerConfig("blacksmith-testbox"),
env: {
OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG: invocationLog,
OPENCLAW_FAKE_CRABBOX_WHOAMI_STATUS: "1",
},
});
expect(result.status).toBe(0);
const invocations = readInvocations(invocationLog);
expect(invocations.filter(([command]) => command === "doctor")).toEqual([]);
expect(invocations.filter(([command]) => command === "whoami")).toEqual([]);
});
it("allows explicit provider runs when broker is ready but another doctor check fails", () => {
const { output } = runSuccessfulBrokerWrapper(["run", "--provider", "aws", "--", "echo ok"], {
env: { OPENCLAW_FAKE_CRABBOX_UNREADY_PROVIDERS: "aws" },
});
expect(output.args).toContain("aws");
});
it.each([
["malformed JSON", "OPENCLAW_FAKE_CRABBOX_MALFORMED_DOCTOR_PROVIDERS"],
["malformed schema", "OPENCLAW_FAKE_CRABBOX_INVALID_DOCTOR_PROVIDERS"],
["provider mismatch", "OPENCLAW_FAKE_CRABBOX_MISMATCHED_DOCTOR_PROVIDERS"],
["missing broker check", "OPENCLAW_FAKE_CRABBOX_MISSING_BROKER_PROVIDERS"],
["inconsistent exit status", "OPENCLAW_FAKE_CRABBOX_INCONSISTENT_DOCTOR_PROVIDERS"],
])("fails closed on %s", (_name, envName) => {
const result = runBrokerWrapper(["run", "--provider", "aws", "--", "echo ok"], {
env: { [envName]: "aws" },
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("provider=aws failed readiness for OpenClaw proof");
});
it("accepts managed broker token-command auth when doctor is healthy", () => {
const { output } = runSuccessfulBrokerWrapper(["run", "--provider", "aws", "--", "echo ok"], {
configJson: managedBrokerConfig("aws", { brokerAuth: "command" }),
});
expect(output.args).toContain("aws");
});
it("lets doctor own its timeout and parses machine output from stdout", () => {
const { output } = runSuccessfulBrokerWrapper(["run", "--provider", "aws", "--", "echo ok"], {
env: {
OPENCLAW_FAKE_CRABBOX_DOCTOR_DELAY_MS: "250",
OPENCLAW_FAKE_CRABBOX_DOCTOR_PROGRESS: "checking provider readiness",
},
nodePreload: testTimingPreload({ spawnTimeoutMs: 100 }),
});
expect(output.args).toContain("aws");
});
it("probes native Windows readiness with the requested target context", () => {
const { output, result } = runSuccessfulBrokerWrapper(
[
"run",
"--workload",
"ci-proof",
"--target",
"windows",
"--windows-mode",
"normal",
"--",
"echo ok",
],
{
env: {
OPENCLAW_FAKE_CRABBOX_EXPECT_DOCTOR_TARGET: "windows",
OPENCLAW_FAKE_CRABBOX_EXPECT_DOCTOR_WINDOWS_MODE: "normal",
},
},
);
expect(output.args).toContain("azure");
expect(result.stderr).toContain("chain=azure,aws");
});
it("rejects the Windows workload without a Windows target", () => {
const result = runBrokerWrapper(["run", "--workload", "windows", "--", "echo ok"], {});
expect(result.status).toBe(2);
expect(result.stderr).toContain("workload=windows requires target=windows");
expect(result.stdout).toBe("");
});
it("preserves following options when workload has no value", () => {
const result = runBrokerWrapper(
["run", "--workload", "--target", "windows", "--", "echo ok"],
{},
);
expect(result.status).toBe(2);
expect(result.stderr).toContain("--workload requires a value");
expect(result.stderr).not.toContain('unsupported Crabbox workload "--target"');
expect(result.stdout).toBe("");
});
it("trusts doctor readiness over stale local broker mode", () => {
const { output } = runSuccessfulBrokerWrapper(
["run", "--workload", "desktop", "--", "echo ok"],
{
configJson: managedBrokerConfig("azure", {
target: "linux",
windowsMode: "normal",
brokerMode: "registered",
}),
},
);
expect(output.args).toContain("azure");
});
it("ignores direct-cloud debugging during automatic readiness checks", () => {
const result = runBrokerWrapper(["run", "--workload", "desktop", "--", "echo ok"], {
configJson: { coordinator: "", brokerAuth: "missing" },
env: {
OPENCLAW_CRABBOX_ALLOW_DIRECT_CLOUD: "1",
OPENCLAW_FAKE_CRABBOX_MISSING_BROKER_PROVIDERS: "azure,aws",
},
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("no ready provider for workload=desktop");
expect(result.stderr).toContain("provider readiness azure:doctor exited 1");
});
it("does not treat an injected Azure Windows default as direct intent", () => {
const result = runBrokerWrapper(["run", "--target", "windows", "--", "echo ok"], {
configJson: directBrokerConfig("blacksmith-testbox"),
env: {
OPENCLAW_CRABBOX_ALLOW_DIRECT_CLOUD: "1",
OPENCLAW_FAKE_CRABBOX_MISSING_BROKER_PROVIDERS: "azure",
},
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("provider=azure failed readiness for OpenClaw proof");
});
it("keeps workload configuration away from administrative commands", () => {
const result = runDefaultWrapper(["--version"], {
env: { OPENCLAW_CRABBOX_WORKLOAD: "ci-fast" },
});
expect(result.status).toBe(0);
expect(result.stdout.trim()).toBe("crabbox 0.56.0");
expect(result.stderr).not.toContain("route workload=");
});
it("does not validate workload flags on administrative commands", () => {
const result = runDefaultWrapper(["--version", "--workload", "surprise"]);
expect(result.status).toBe(0);
expect(result.stdout.trim()).toBe("crabbox 0.56.0");
expect(result.stderr).not.toContain("unsupported Crabbox workload");
});
it("keeps explicit provider choices outside automatic routing", () => {
const { output, result } = runSuccessfulBrokerWrapper(
["run", "--provider", "azure", "--workload", "interactive", "--", "echo ok"],
{
env: {
OPENCLAW_FAKE_CRABBOX_UNREADY_PROVIDERS: "azure",
},
},
);
expect(output.args).toContain("azure");
expect(result.stderr).not.toContain("route workload=");
});
it.each([
{
name: "explicit",
args: ["run", "--provider", "blacksmith-testbox", "--workload", "untrusted", "--", "echo ok"],
env: {},
},
{
name: "environment",
args: ["run", "--workload", "untrusted", "--", "echo ok"],
env: { CRABBOX_PROVIDER: "blacksmith-testbox" },
},
])("rejects $name providers outside the workload eligibility policy", ({ args, env }) => {
const result = runBrokerWrapper(args, {
env: {
...(env as Record<string, string>),
},
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain(
"provider=blacksmith-testbox is not eligible for workload=untrusted; allowed=azure,aws",
);
});
it("requires broker auth for explicit providers inside workload routing", () => {
const result = runBrokerWrapper(
["run", "--provider", "azure", "--workload", "desktop", "--", "echo ok"],
{
configJson: directBrokerConfig("azure"),
env: { OPENCLAW_FAKE_CRABBOX_MISSING_BROKER_PROVIDERS: "azure" },
},
);
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("provider=azure failed readiness for OpenClaw proof");
});
it.each(["aws", "azure"])(
"does not let direct overrides weaken implicit managed %s config",
(provider) => {
const result = runBrokerWrapper(["run", "--", "echo ok"], {
configJson: managedBrokerConfig(provider, { brokerAuth: "missing" }),
env: {
OPENCLAW_CRABBOX_ALLOW_DIRECT_CLOUD: "1",
OPENCLAW_FAKE_CRABBOX_UNAUTHORIZED_PROVIDERS: provider,
},
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain(
`provider=${provider} requires managed Crabbox broker authentication`,
);
},
);
it("still validates workloads when a provider is explicit", () => {
const result = runWrapper(brokerProviderHelp, [
"run",
"--provider",
"azure",
"--workload",
"surprise",
"--",
"echo ok",
]);
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain('unsupported Crabbox workload "surprise"');
});
it.each(["azure"])(
"allows opted-in explicit direct %s commands outside workload routing",
(provider) => {
const { output } = runSuccessfulBrokerWrapper(
["run", "--provider", provider, "--", "echo ok"],
{
configJson: managedBrokerConfig(provider, { brokerAuth: "missing" }),
env: {
OPENCLAW_CRABBOX_ALLOW_DIRECT_CLOUD: "1",
},
},
);
expect(output.args).toContain(provider);
},
);
it.each(["azure"])("requires direct-cloud opt-in for explicit %s commands", (provider) => {
const result = runBrokerWrapper(["run", "--provider", provider, "--", "echo ok"], {
configJson: directBrokerConfig(provider),
env: { OPENCLAW_FAKE_CRABBOX_UNAUTHORIZED_PROVIDERS: provider },
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain(
`provider=${provider} requires managed Crabbox broker authentication`,
);
expect(result.stderr).toContain(
`direct ${provider} debugging requires an original \`--provider ${provider}\`, no \`--workload\``,
);
});
it.each(["daytona"])(
"does not treat CRABBOX_PROVIDER=%s as explicit direct intent",
(provider) => {
const result = runBrokerWrapper(["run", "--", "echo ok"], {
configJson: directBrokerConfig(provider),
env: {
CRABBOX_PROVIDER: provider,
OPENCLAW_CRABBOX_ALLOW_DIRECT_CLOUD: "1",
OPENCLAW_FAKE_CRABBOX_MISSING_BROKER_PROVIDERS: provider,
},
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain(`provider=${provider} failed readiness for OpenClaw proof`);
},
);
it("fails closed when no policy provider is ready", () => {
const result = runBrokerWrapper(["run", "--workload", "ci-fast", "--", "echo ok"], {
env: {
OPENCLAW_FAKE_CRABBOX_UNREADY_PROVIDERS: "blacksmith-testbox,daytona,azure,aws",
},
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("no ready provider for workload=ci-fast");
expect(result.stderr).toContain("provider readiness");
expect(result.stderr).toContain('{"ok":false,"provider":"blacksmith-testbox","checks":');
expect(result.stderr).toMatch(
/recovery: run `\S+crabbox doctor --provider blacksmith-testbox --json`/u,
);
});
it("hints at lease expiry when a reused-lease run fails fast", () => {
const result = runDefaultWrapper(
["run", "--provider", "local-container", "--id", "tbx_expired_fixture", "--", "echo ok"],
{ env: { OPENCLAW_FAKE_CRABBOX_RUN_STATUS: "1" } },
);
expect(result.status).toBe(1);
expect(result.stderr).toContain(
"run --id tbx_expired_fixture failed fast; reusable leases expire after their idle timeout",
);
});
it("keeps failed runs without a reused lease free of the expiry hint", () => {
const result = runDefaultWrapper(["run", "--provider", "local-container", "--", "echo ok"], {
env: { OPENCLAW_FAKE_CRABBOX_RUN_STATUS: "1" },
});
expect(result.status).toBe(1);
expect(result.stderr).not.toContain("failed fast; reusable leases expire");
});
it.each([["-no-sync=true"], ["--no-sync=false"], ["--id", "tbx_unused", "--no-sync"]])(
"rejects unsupported Testbox sync flags before delegation: %j",
(...flags) => {
const invocationLog = makeInvocationLog();
const result = runDefaultWrapper(["run", ...flags, "--", "echo ok"], {
configJson: { provider: "blacksmith-testbox" },
env: {
OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG: invocationLog,
OPENCLAW_FAKE_CRABBOX_RUN_STATUS: "99",
},
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("provider=blacksmith-testbox does not support --no-sync");
expect(readInvocations(invocationLog).filter(([command]) => command === "run")).toEqual([]);
},
);
it.each([
{ provider: "blacksmith-testbox", flags: ["--script", "missing-script.sh"] },
{ provider: "blacksmith-testbox", flags: ["--script=missing-script.sh"] },
{ provider: "blacksmith-testbox", flags: ["--script-stdin"] },
{ provider: "blacksmith", flags: ["--script-stdin=true"] },
{ provider: "blacksmith-testbox", flags: ["--script-stdin=false", "--script-stdin=true"] },
{ provider: "blacksmith-testbox", flags: ["--script=missing-script.sh", "--script-stdin"] },
{ provider: "blacksmith-testbox", flags: ["--script-stdin", "--script=missing-script.sh"] },
])(
"rejects uploaded Testbox scripts before source or lease work: $provider $flags",
({ provider, flags }) => {
const invocationLog = makeInvocationLog();
const result = runDefaultWrapper(["run", "--provider", provider, ...flags], {
input: "echo uploaded-script-must-not-run\n",
env: { OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG: invocationLog },
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("does not support --script or --script-stdin");
expect(result.stderr).toContain("synced script as trailing argv");
expect(result.stderr).not.toContain("syncing from temporary full checkout");
expect(
readInvocations(invocationLog).filter(
(args) =>
["sync-plan", "run", "warmup"].includes(args[0] ?? "") &&
!(args.length === 2 && args[0] === "run" && args[1] === "--help"),
),
).toEqual([]);
},
);
it.each([
["--script-stdin=false"],
["--script="],
["--script-stdin=true", "--script-stdin=false"],
["--label", "--script-stdin"],
])("keeps disabled or value-only script flags on Testbox command argv: %j", (...flags) => {
const { output, remoteCommand } = runSuccessfulDefaultWrapper(
["run", "--provider", "blacksmith-testbox", ...flags, "--", "echo", "--script-stdin"],
{ input: "must-not-be-read-as-script\n" },
);
expect(output.scriptContent).toBe("");
expect(output.args).not.toContain("--script");
expect(remoteCommand).toContain("echo --script-stdin");
for (const flag of flags) {
expect(output.args).toContain(flag);
}
});
it.each([
["--script-stdin="],
["--script-stdin=invalid"],
["--script-stdin=invalid", "--script-stdin=true"],
["--script-stdin=true", "--script-stdin=invalid"],
["--script=missing-script.sh", "--script-stdin"],
["--script-stdin", "--script=missing-script.sh"],
])("preserves invalid or conflicting script flags for Crabbox rejection: %j", (...flags) => {
const invocationLog = makeInvocationLog();
// Record the delegated argv and refuse it before the fake transport reads files.
const result = runDefaultWrapper(
[
"run",
"--provider",
"aws",
"--target",
"macos",
...flags,
"--",
"node",
"scripts/check-changed.mjs",
],
{
input: "must-not-be-consumed-as-script\n",
env: {
OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG: invocationLog,
OPENCLAW_FAKE_CRABBOX_RUN_STATUS: "2",
},
},
);
expect(result.status).toBe(2);
const runs = readInvocations(invocationLog).filter(
(args) => args[0] === "run" && !(args.length === 2 && args[1] === "--help"),
);
expect(runs).toHaveLength(1);
// The delegated cwd changes; local file operands retain their original repo root.
const expectedFlags = flags.map((flag) =>
flag === "--script=missing-script.sh"
? `--script=${path.join(repoRoot, "missing-script.sh")}`
: flag,
);
const args = runs[0]!;
const first = args.indexOf(expectedFlags[0]!);
expect(first).toBeGreaterThanOrEqual(0);
expect(args.slice(first, first + expectedFlags.length)).toEqual(expectedFlags);
expect(args.filter((arg) => /^--script(?:-stdin)?(?:=|$)/u.test(arg))).toEqual(
expectedFlags.filter((arg) => /^--script(?:-stdin)?(?:=|$)/u.test(arg)),
);
});
it.each([
{ provider: "aws", flags: ["--no-sync"], command: ["echo", "ok"] },
{ provider: "blacksmith-testbox", flags: [], command: ["echo", "--no-sync"] },
{ provider: "blacksmith-testbox", flags: ["--label", "--no-sync"], command: ["echo", "ok"] },
])(
"preserves sync flags outside Testbox run options: $provider $flags $command",
({ provider, flags, command }) => {
const { output } = runSuccessfulDefaultWrapper([
"run",
"--provider",
provider,
...flags,
"--",
...command,
]);
if (flags.length > 0) {
expect(output.args).toContain("--no-sync");
} else {
expect(output.args.at(-1)).toContain("echo --no-sync");
}
},
);
it.each([
{
flags: ["--fresh-pr", "openclaw/openclaw#1"],
command: ["node", "scripts/source-fixture.mjs"],
},
{
flags: ["--fresh-pr=openclaw/openclaw#1", "--apply-local-patch"],
command: ["node", "scripts/check-changed.mjs"],
},
{ flags: ["--no-sync"], command: ["node", "scripts/source-fixture.mjs"] },
{ flags: ["--sync-only"], command: [] },
{ flags: ["--target", "windows"], command: ["node", "scripts/source-fixture.mjs"] },
])("preserves native AWS source selection for $flags", ({ flags, command }) => {
const { output, remoteCommand } = runSuccessfulDefaultWrapper(
["run", "--provider", "aws", ...flags, "--", ...command],
{
gitResponses: {
[GIT_CONFIG_SPARSE_KEY]: { stdout: "true\n" },
[GIT_STATUS_PORCELAIN_KEY]: { stdout: "" },
},
},
);
expect(remoteCommand).not.toContain(".openclaw-crabbox-changed-gate.bundle");
if (flags.some((flag) => flag.startsWith("--fresh-pr")) || flags.includes("--no-sync")) {
expect(output.cwd).toBe(repoRoot);
}
for (const flag of flags) {
expect(output.args).toContain(flag);
}
});
it.skipIf(process.platform === "win32").each(["command", "script"])(
"runs native AWS no-hydrate %s payloads without Node or pnpm",
(mode) => {
const cwd = invocationLogTempDirs.make("openclaw-raw-aws-");
const emptyPath = path.join(cwd, "empty-path");
mkdirSync(emptyPath);
const script =
"set -eu\n! command -v node\n! command -v pnpm\nprintf '%s\\n' raw-bootstrap-ran\n";
const file = path.join(cwd, "bootstrap.sh");
writeFileSync(file, script);
const payload = mode === "script" ? ["--script", file] : ["--shell", "--", script];
const { output, remoteCommand } = runSuccessfulDefaultWrapper([
"run",
"--provider",
"aws",
"--no-hydrate",
...payload,
]);
expect(output.args).toContain("--no-hydrate");
const result = spawnSync(
"/bin/sh",
["-c", mode === "script" ? output.scriptContent! : remoteCommand],
{
cwd,
encoding: "utf8",
env: { PATH: emptyPath },
},
);
expect(result.status, result.stderr).toBe(0);
expect(result.stdout).toBe("raw-bootstrap-ran\n");
expect(output.args.slice(-payload.length)).toEqual(payload);
expect(mode === "script" ? output.scriptContent : remoteCommand).toBe(script);
},
);
it.each([
{ flags: ["--no-hydrate"], capsule: false },
{ flags: ["--no-hydrate=true"], capsule: false },
{ flags: ["--no-hydrate=1"], capsule: false },
{ flags: ["--no-hydrate=T"], capsule: false },
{ flags: ["--no-hydrate=false"], capsule: true },
{ flags: ["--no-hydrate=0"], capsule: true },
{ flags: ["--no-hydrate=F"], capsule: true },
{ flags: ["--no-hydrate", "--no-hydrate=false"], capsule: true },
{ flags: ["--no-hydrate=false", "--no-hydrate"], capsule: false },
{ flags: ["--label", "--no-hydrate"], capsule: true },
])("preserves native hydration option semantics: $flags", ({ flags, capsule }) => {
const { output, remoteCommand } = runSuccessfulDefaultWrapper([
"run",
"--provider",
"aws",
...flags,
"--",
"echo",
"--no-hydrate",
]);
expect(remoteCommand.includes(".openclaw-crabbox-changed-gate.bundle")).toBe(capsule);
expect(remoteCommand).not.toContain("OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1");
const index = output.args.indexOf(flags[0]!);
expect(output.args.slice(index, index + flags.length)).toEqual(flags);
if (capsule) {
expect(remoteCommand).toContain("echo --no-hydrate");
} else {
expect(output.args.slice(-2)).toEqual(["echo", "--no-hydrate"]);
}
});
it.each<{ source: string; args: string[]; options: WrapperOptions; capsule: boolean }>([
{ source: "Linux alias", args: ["--target", "ubuntu"], options: {}, capsule: true },
{
source: "Linux environment",
args: [],
options: { env: { CRABBOX_TARGET: "linux" } },
capsule: true,
},
{
source: "macOS environment",
args: [],
options: { env: { CRABBOX_TARGET_OS: "darwin" } },
capsule: false,
},
{
source: "macOS config",
args: [],
options: { configJson: managedBrokerConfig("aws", { target: "osx" }) },
capsule: false,
},
{
source: "WSL2",
args: ["--target", "windows", "--windows-mode", "wsl2"],
options: {},
capsule: false,
},
])(
"uses the effective $source target for ordinary AWS capsules",
({ args, options, capsule }) => {
const { output, remoteCommand } = runSuccessfulDefaultWrapper(
["run", "--provider", "aws", ...args, "--", "echo", "ok"],
options,
);
expect(remoteCommand.includes(".openclaw-crabbox-changed-gate.bundle")).toBe(capsule);
expect(remoteCommand).not.toContain("OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1");
if (!capsule) {
expect(output.args.slice(-2)).toEqual(["echo", "ok"]);
}
},
);
it.skipIf(process.platform === "win32").each(["missing", "overlapping"])(
"rejects a %s Testbox workspace binding before running the payload",
(binding) => {
const { remoteCommand } = runSuccessfulDefaultWrapper([
"run",
"--provider",
"blacksmith-testbox",
"--",
"echo",
"payload-ran",
]);
const cwd = realpathSync(invocationLogTempDirs.make("openclaw-unprepared-testbox-"));
if (binding === "overlapping") {
mkdirSync(path.join(cwd, ".git"));
symlinkSync(cwd, path.join(cwd, ".git", "crabbox-artifact-root"), "dir");
}
const result = spawnSync("bash", ["-c", remoteCommand], { cwd, encoding: "utf8" });
expect(result.status).toBe(2);
expect(result.stderr).toContain(
binding === "missing"
? "missing prepared Testbox execution workspace"
: "workspaces overlap",
);
expect(result.stdout).not.toContain("payload-ran");
expect(readdirSync(cwd)).toEqual(binding === "missing" ? [] : [".git"]);
},
);
it("tells operators how to read delegated Testbox proof status", () => {
const result = runDefaultWrapper(["run", "--provider", "blacksmith-testbox", "--", "echo ok"]);
expect(result.status).toBe(0);
expect(result.stderr).toContain("delegated Testbox proof uses the wrapper exitCode");
expect(result.stderr).toContain("Actions run can show cancelled during external lease cleanup");
});
it.each([false, true])(
"rejects reused Testboxes without a key at the selected root (custom state=%s)",
(customState) => {
const home = invocationLogTempDirs.make("openclaw-crabbox-home-");
const env = testHomeEnv(home);
if (customState) {
env.XDG_STATE_HOME = path.join(home, "selected state");
env.OPENCLAW_FAKE_CRABBOX_VERSION = "crabbox 0.66.0";
const legacyKey = path.join(
testCrabboxConfigDir(home),
"testboxes",
"tbx_direct",
"id_ed25519",
);
mkdirSync(path.dirname(legacyKey), { recursive: true });
writeFileSync(legacyKey, "fake test key\n", "utf8");
}
const result = runDefaultWrapper(
["run", "--provider", "blacksmith-testbox", "--id", "tbx_direct", "--", "echo ok"],
{ env },
);
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("provider=blacksmith-testbox --id tbx_direct");
expect(result.stderr).toContain("has no Crabbox SSH key");
expect(result.stderr).toContain("direct `blacksmith testbox warmup` leases");
},
);
it.each([
{ id: "tbx_owned", createKey: true, state: "", version: "0.56.0", selectedKey: false },
{ id: "tbx_legacy", createKey: true, state: "state", version: "0.57.0", selectedKey: false },
{ id: "tbx_first", createKey: true, state: "state", version: "0.58.0", selectedKey: true },
{ id: "tbx_default", createKey: true, state: "", version: "0.66.0", selectedKey: false },
{ id: "tbx_selected", createKey: true, state: "state", version: "0.66.0", selectedKey: true },
{ id: "blue-hermit", createKey: false, state: "", version: "0.66.0", selectedKey: false },
...(process.platform === "win32"
? [
{
id: "tbx_namespaced",
createKey: true,
state: "namespaced",
version: "0.66.0",
selectedKey: true,
},
]
: []),
])(
"delegates reusable Testbox identity $id to Crabbox $version",
({ id, createKey, state, version, selectedKey }) => {
const home = invocationLogTempDirs.make("openclaw-crabbox-home-");
const directory = path.join(home, state);
const env = {
...testHomeEnv(home),
XDG_STATE_HOME: state
? state === "namespaced"
? path.toNamespacedPath(directory)
: directory
: "",
OPENCLAW_FAKE_CRABBOX_VERSION: `crabbox ${version}`,
};
const keyRoot = selectedKey
? path.join(env.XDG_STATE_HOME, "crabbox")
: testCrabboxConfigDir(home);
if (createKey) {
const keyPath = path.join(keyRoot, "testboxes", id, "id_ed25519");
mkdirSync(path.dirname(keyPath), { recursive: true });
writeFileSync(keyPath, "fake test key\n", "utf8");
}
const { output } = runSuccessfulDefaultWrapper(
["run", "--provider", "blacksmith-testbox", "--id", id, "--", "echo ok"],
{ env },
);
expect(output.args).toEqual([
"run",
"--provider",
"blacksmith-testbox",
"--id",
id,
"--reclaim",
"--shell",
"--",
expect.stringContaining("'echo ok'"),
]);
},
);
it.each([
"relative-state",
" ",
...(process.platform === "win32" ? ["C:state", "\\state", "/state"] : []),
])("rejects the relative lease key root %j before running a reused Testbox", (stateRoot) => {
const home = invocationLogTempDirs.make("openclaw-crabbox-home-");
const id = "tbx_relative";
const legacyKey = path.join(testCrabboxConfigDir(home), "testboxes", id, "id_ed25519");
mkdirSync(path.dirname(legacyKey), { recursive: true });
writeFileSync(legacyKey, "fake test key\n", "utf8");
const result = runDefaultWrapper(
["run", "--provider", "blacksmith-testbox", "--id", id, "--", "echo ok"],
{
env: {
...testHomeEnv(home),
XDG_STATE_HOME: stateRoot,
OPENCLAW_FAKE_CRABBOX_VERSION: "crabbox 0.66.0",
},
},
);
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("XDG_STATE_HOME must be absolute");
});
it.each([
{ version: "0.57.0", stateDirectory: "state" },
{ version: "0.66.0", stateDirectory: "state" },
{ version: "0.57.0", stateDirectory: "state " },
])(
"fails before reuse when a Blacksmith Testbox is claimed by another repo ($version, $stateDirectory)",
({ version, stateDirectory }) => {
const home = invocationLogTempDirs.make("openclaw-crabbox-home-");
const id = "tbx_claimed";
const stateRoot = path.join(home, ".local", stateDirectory);
const keyRoot =
version === "0.66.0" ? path.join(stateRoot, "crabbox") : testCrabboxConfigDir(home);
const keyPath = path.join(keyRoot, "testboxes", id, "id_ed25519");
mkdirSync(path.dirname(keyPath), { recursive: true });
writeFileSync(keyPath, "fake test key\n", "utf8");
const claimPath = path.join(stateRoot, "crabbox", "claims", `${id}.json`);
mkdirSync(path.dirname(claimPath), { recursive: true });
writeFileSync(
claimPath,
`${JSON.stringify({ leaseID: id, repoRoot: "/tmp/other-repo" })}\n`,
"utf8",
);
const result = runDefaultWrapper(
["run", "--provider", "blacksmith-testbox", "--id", id, "--", "echo ok"],
{
env: {
...testHomeEnv(home),
XDG_STATE_HOME: stateRoot,
OPENCLAW_FAKE_CRABBOX_VERSION: `crabbox ${version}`,
},
},
);
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain(`lease ${id} is claimed by repo /tmp/other-repo`);
expect(result.stderr).toContain(`use --reclaim to claim it for ${repoRoot}`);
const reclaimed = runDefaultWrapper(
["run", "--provider", "blacksmith-testbox", "--id", id, "--reclaim", "--", "echo ok"],
{
env: {
...testHomeEnv(home),
XDG_STATE_HOME: stateRoot,
OPENCLAW_FAKE_CRABBOX_VERSION: `crabbox ${version}`,
},
},
);
expect(reclaimed.status).toBe(0);
expect(parseFakeCrabboxOutput(reclaimed).args).toContain("--reclaim");
},
);
it.each([
{ label: "successful", status: 0 },
{ label: "failed", status: 7 },
])("restores delegated Blacksmith claims after $label runs", ({ status }) => {
const home = makeTempDir(tempDirs, "openclaw-crabbox-home-", tmpdir());
const id = `tbx_restore_${status}`;
const keyPath = path.join(testCrabboxConfigDir(home), "testboxes", id, "id_ed25519");
mkdirSync(path.dirname(keyPath), { recursive: true });
writeFileSync(keyPath, "fake test key\n", "utf8");
const stateRoot = path.join(home, ".local", "state");
const claimPath = path.join(stateRoot, "crabbox", "claims", `${id}.json`);
mkdirSync(path.dirname(claimPath), { recursive: true });
const originalClaim = {
leaseID: id,
repoRoot,
owner: "preserved-owner",
metadata: { keep: true },
};
writeFileSync(claimPath, `${JSON.stringify(originalClaim)}\n`, "utf8");
const result = runDelegatedBlacksmith(
["run", "--provider", "blacksmith-testbox", "--id", id, "--", "echo ok"],
{
...testHomeEnv(home),
XDG_STATE_HOME: stateRoot,
OPENCLAW_FAKE_CRABBOX_CLAIM_PATH: claimPath,
...(status > 0 ? { OPENCLAW_FAKE_CRABBOX_RUN_STATUS: String(status) } : {}),
},
);
expect(result.status).toBe(status);
expect(JSON.parse(readFileSync(claimPath, "utf8"))).toEqual(originalClaim);
});
it("restores a created delegated Blacksmith claim by captured timing lease id", () => {
const home = makeTempDir(tempDirs, "openclaw-crabbox-home-", tmpdir());
const stateRoot = path.join(home, ".local", "state");
const claimsDir = path.join(stateRoot, "crabbox", "claims");
const id = "tbx_created_timing";
const claimPath = path.join(claimsDir, `${id}.json`);
const decoyPath = path.join(claimsDir, "tbx_created_decoy.json");
const originalClaim = { leaseID: id, repoRoot, metadata: { keep: true } };
const decoyClaim = { leaseID: "tbx_created_decoy", repoRoot };
mkdirSync(claimsDir, { recursive: true });
writeFileSync(claimPath, `${JSON.stringify(originalClaim)}\n`, "utf8");
writeFileSync(decoyPath, `${JSON.stringify(decoyClaim)}\n`, "utf8");
const result = runDelegatedBlacksmith(
["run", "--provider", "blacksmith-testbox", "--keep", "--timing-json", "--", "echo ok"],
{
...testHomeEnv(home),
XDG_STATE_HOME: stateRoot,
OPENCLAW_FAKE_CRABBOX_CLAIM_PATH: claimPath,
OPENCLAW_FAKE_CRABBOX_EXTRA_CLAIM_PATH: decoyPath,
OPENCLAW_FAKE_CRABBOX_TIMING_LEASE_ID: id,
},
);
expect(result.status).toBe(0);
expect(JSON.parse(readFileSync(claimPath, "utf8"))).toEqual(originalClaim);
expect(JSON.parse(readFileSync(decoyPath, "utf8"))).toEqual({
...decoyClaim,
repoRoot: parseFakeCrabboxOutput(result).cwd,
});
});
it("restores created delegated Blacksmith claims from the temporary checkout fallback", () => {
const home = makeTempDir(tempDirs, "openclaw-crabbox-home-", tmpdir());
const stateRoot = path.join(home, ".local", "state");
const claimsDir = path.join(stateRoot, "crabbox", "claims");
const claimPath = path.join(claimsDir, "tbx_created_fallback.json");
const siblingPath = path.join(claimsDir, "tbx_created_sibling.json");
const foreignPath = path.join(claimsDir, "tbx_foreign_fallback.json");
const createdClaim = { leaseID: "tbx_created_fallback", repoRoot, owner: "created" };
const siblingClaim = { leaseID: "tbx_created_sibling", repoRoot, owner: "sibling" };
const foreignClaim = {
leaseID: "tbx_foreign_fallback",
repoRoot: "/tmp/genuinely-foreign-repo",
};
mkdirSync(claimsDir, { recursive: true });
writeFileSync(claimPath, `${JSON.stringify(createdClaim)}\n`, "utf8");
writeFileSync(siblingPath, `${JSON.stringify(siblingClaim)}\n`, "utf8");
writeFileSync(foreignPath, `${JSON.stringify(foreignClaim)}\n`, "utf8");
const result = runDelegatedBlacksmith(
["run", "--provider", "blacksmith-testbox", "--keep", "--", "echo ok"],
{
...testHomeEnv(home),
XDG_STATE_HOME: stateRoot,
OPENCLAW_FAKE_CRABBOX_CLAIM_PATH: claimPath,
OPENCLAW_FAKE_CRABBOX_EXTRA_CLAIM_PATH: siblingPath,
},
);
expect(result.status).toBe(0);
expect(JSON.parse(readFileSync(claimPath, "utf8"))).toEqual(createdClaim);
expect(JSON.parse(readFileSync(siblingPath, "utf8"))).toEqual(siblingClaim);
expect(JSON.parse(readFileSync(foreignPath, "utf8"))).toEqual(foreignClaim);
});
it("restores a failed delegated Blacksmith claim kept on failure", () => {
const home = makeTempDir(tempDirs, "openclaw-crabbox-home-", tmpdir());
const stateRoot = path.join(home, ".local", "state");
const claimPath = path.join(stateRoot, "crabbox", "claims", "tbx_created_failure.json");
const originalClaim = {
leaseID: "tbx_created_failure",
repoRoot,
metadata: { keepOnFailure: true },
};
mkdirSync(path.dirname(claimPath), { recursive: true });
writeFileSync(claimPath, `${JSON.stringify(originalClaim)}\n`, "utf8");
const result = runDelegatedBlacksmith(
["run", "--provider", "blacksmith-testbox", "--keep-on-failure", "--", "false"],
{
...testHomeEnv(home),
XDG_STATE_HOME: stateRoot,
OPENCLAW_FAKE_CRABBOX_CLAIM_PATH: claimPath,
OPENCLAW_FAKE_CRABBOX_RUN_STATUS: "7",
},
);
expect(result.status).toBe(7);
expect(JSON.parse(readFileSync(claimPath, "utf8"))).toEqual(originalClaim);
});
it("leaves genuinely foreign delegated Blacksmith claims untouched", () => {
const home = makeTempDir(tempDirs, "openclaw-crabbox-home-", tmpdir());
const id = "tbx_foreign_claim";
const keyPath = path.join(testCrabboxConfigDir(home), "testboxes", id, "id_ed25519");
mkdirSync(path.dirname(keyPath), { recursive: true });
writeFileSync(keyPath, "fake test key\n", "utf8");
const stateRoot = path.join(home, ".local", "state");
const claimPath = path.join(stateRoot, "crabbox", "claims", `${id}.json`);
mkdirSync(path.dirname(claimPath), { recursive: true });
const foreignClaim = {
leaseID: id,
repoRoot: "/tmp/genuinely-foreign-repo",
owner: "foreign-owner",
};
writeFileSync(claimPath, `${JSON.stringify({ ...foreignClaim, repoRoot })}\n`, "utf8");
const result = runDelegatedBlacksmith(
["run", "--provider", "blacksmith-testbox", "--id", id, "--", "echo ok"],
{
...testHomeEnv(home),
XDG_STATE_HOME: stateRoot,
OPENCLAW_FAKE_CRABBOX_CLAIM_PATH: claimPath,
OPENCLAW_FAKE_CRABBOX_CLAIM_REPO_ROOT: foreignClaim.repoRoot,
},
);
expect(result.status).toBe(0);
expect(JSON.parse(readFileSync(claimPath, "utf8"))).toEqual(foreignClaim);
});
it.skipIf(process.platform === "win32").each([0, 43])(
"executes the named Testbox job with frozen preparation (install exit %s)",
(installExit) => {
const root = invocationLogTempDirs.make("openclaw-testbox-job-");
const events = path.join(root, "events");
writeShellCommand(
path.join(root, "corepack"),
`
case "$*" in
'pnpm install --frozen-lockfile')
printf 'install\\n' >> ${shellQuote(events)}
printf 'install diagnostics\\n'
exit ${installExit}
;;
'pnpm check:changed')
printf 'payload\\n' >> ${shellQuote(events)}
printf 'payload\\n'
;;
*) exit 64 ;;
esac
`,
);
const config = parse(readFileSync(path.join(repoRoot, ".crabbox.yaml"), "utf8")) as {
jobs: { "testbox-changed": { shell?: boolean; command: string } };
};
const job = config.jobs["testbox-changed"];
// Crabbox jobRunArgs uses strings.Fields unless shell is enabled.
const command = job.shell
? job.command
: job.command.trim().split(/\s+/u).map(shellQuote).join(" ");
const result = spawnSync("bash", ["-c", command], {
cwd: root,
encoding: "utf8",
timeout: 10_000,
env: { ...process.env, PATH: [root, process.env.PATH].join(path.delimiter) },
});
expect(result.status, result.stderr).toBe(installExit);
expect(result.stdout).toBe(installExit ? "" : "payload\n");
expect(result.stderr).toContain("install diagnostics");
expect(readFileSync(events, "utf8")).toBe(installExit ? "install\n" : "install\npayload\n");
},
);
it("exports CI for complete Blacksmith Testbox shell snippets", () => {
const { output } = runSuccessfulDefaultWrapper([
"run",
"--provider",
"blacksmith-testbox",
"--shell",
"--",
"cd packages && pnpm install && pnpm build",
]);
expect(output.args.at(-1)).toContain(remoteTestboxBootstrap);
expect(output.args.at(-1)).not.toContain("remote-testbox-sync");
expect(output.args).toEqual([
"run",
"--provider",
"blacksmith-testbox",
"--shell",
"--",
expect.stringContaining("cd packages && pnpm install && pnpm build"),
]);
});
it("only forces the short local-container Docker work root on Linux", () => {
const { result } = runSuccessfulDefaultWrapper([
"run",
"--provider",
"local-container",
"--",
"echo ok",
]);
const expectedMessage =
"[crabbox] provider=docker using short host-visible work root for OpenClaw Docker tests";
if (process.platform === "linux") {
expect(result.stderr).toContain(expectedMessage);
} else {
expect(result.stderr).not.toContain(expectedMessage);
}
});
it("defaults AWS macOS runs to on-demand capacity", () => {
const { output } = runSuccessfulMacosCommand(["echo ok"]);
expect(output.args).toEqual([
"run",
"--provider",
"aws",
"--target",
"macos",
"--market",
"on-demand",
"--",
"echo ok",
]);
});
it("prefers Azure for unqualified Windows runs", () => {
const { output, result } = runSuccessfulWrapper(
azureProviderHelp,
[
"run",
"--target",
"windows",
"--windows-mode",
"wsl2",
"--",
"corepack",
"pnpm",
"check:changed",
],
{
gitResponses: {
[GIT_STATUS_PORCELAIN_KEY]: { stdout: " M scripts/crabbox-wrapper.mts\n" },
[GIT_MERGE_BASE_MAIN_HEAD_KEY]: { stdout: "abc123\n" },
},
},
);
const remoteCommand = normalizeShellLineEndings(output.scriptContent!);
expect(output.args.slice(0, 7)).toEqual([
"run",
"--target",
"windows",
"--windows-mode",
"wsl2",
"--provider",
"azure",
]);
expect(output.args).toContain("--no-hydrate");
expect(output.args).toContain("--script");
expect(output.args).not.toContain("--shell");
expect(output.args.join(" ")).not.toContain("openclaw_crabbox_bootstrap_wsl2_js");
expect(remoteCommand).toContain("openclaw_crabbox_bootstrap_wsl2_js");
expect(remoteCommand).toContain("node-v${node_version}-linux-${node_arch}.tar.gz");
expect(remoteCommand).toContain("sha256sum -c -");
expect(remoteCommand).toContain("corepack enable --install-directory");
expect(remoteCommand).toContain("install-dependencies.sh");
expect(remoteCommand).toContain("openclaw_crabbox_bootstrap_wsl2_js || exit $?");
expectChangedGateGitBootstrap(remoteCommand);
expect(remoteCommand.indexOf("node --version >&2 || return 1")).toBeLessThan(
remoteCommand.indexOf("node -e"),
);
expect(remoteCommand.indexOf("corepack enable --install-directory")).toBeLessThan(
remoteCommand.indexOf("node -e"),
);
expect(remoteCommand.indexOf("node -e")).toBeLessThan(
remoteCommand.indexOf("pnpm --version >&2"),
);
expect(remoteCommand).toContain(
`{ openclaw_crabbox_env ${remoteChangedGateEnvPrefix} corepack pnpm check:changed\n}`,
);
expect(output.cwd).toContain("openclaw-crabbox-sync-");
expect(result.stderr).toContain("overlaying the local worktree as changes from abc123");
expect(result.stderr).toContain("provider=azure");
});
it("keeps WSL2 non-JavaScript commands on the default hydrate path", () => {
const { output } = runSuccessfulWrapper(azureProviderHelp, [
"run",
"--target",
"windows",
"--windows-mode",
"wsl2",
"--",
"echo",
"ok",
]);
expect(output.args).toEqual([
"run",
"--target",
"windows",
"--windows-mode",
"wsl2",
"--provider",
"azure",
"--",
"echo",
"ok",
]);
expect(output.args).not.toContain("--no-hydrate");
expect(output.args).not.toContain("--shell");
});
it.each([
{ scenario: "Azure advertised", help: azureProviderHelp, leaseArgs: [] },
{ scenario: "Azure unavailable", help: defaultProviderHelp, leaseArgs: [] },
{ scenario: "existing lease", help: azureProviderHelp, leaseArgs: ["--id", "cbx_existing"] },
])("preserves the Windows provider env override with $scenario", ({ help, leaseArgs }) => {
const args = ["run", ...leaseArgs, "--target", "windows", "--", "echo ok"];
const { output, result } = runSuccessfulWrapper(help, args, {
env: { CRABBOX_PROVIDER: "aws" },
});
expect(output.args).toEqual(args);
expect(result.stderr).toContain("provider=aws");
});
it("uses the native Windows daemon job for Windows hydrate actions", () => {
const { output } = runSuccessfulWindowsHydrate("--id", "cbx_existing");
expect(output.args).toEqual(
windowsHydrateArgs("--id", "cbx_existing", "--job", "hydrate-windows-daemon"),
);
});
it.each([["--field", "--job=custom"]])(
"repairs generic hydrate jobs for native Windows hydrate actions: %j",
(...prefix) => {
const { output } = runSuccessfulWindowsHydrate(
...prefix,
"--job",
"hydrate",
"--id",
"cbx_existing",
);
expect(output.args).toEqual(
windowsHydrateArgs(...prefix, "--job", "hydrate-windows-daemon", "--id", "cbx_existing"),
);
},
);
it.each([["--field", "--job"]])(
"repairs generic hydrate job assignments for native Windows hydrate actions: %j",
(...prefix) => {
const { output } = runSuccessfulWindowsHydrate(
...prefix,
"--job=hydrate",
"--id",
"cbx_existing",
);
expect(output.args).toEqual(
windowsHydrateArgs(...prefix, "--job=hydrate-windows-daemon", "--id", "cbx_existing"),
);
},
);
it("keeps post-delimiter hydrate payloads untouched for native Windows hydrate actions", () => {
const { output } = runSuccessfulWindowsHydrate(
"--id",
"cbx_existing",
"--",
"--job",
"hydrate",
);
expect(output.args).toEqual(
windowsHydrateArgs(
"--id",
"cbx_existing",
"--job",
"hydrate-windows-daemon",
"--",
"--job",
"hydrate",
),
);
});
it("keeps explicit non-native hydrate jobs for Windows hydrate actions", () => {
const args = ["--job", "hydrate-github", "--id", "cbx_existing"];
const { output } = runSuccessfulWindowsHydrate(...args);
expect(output.args).toEqual(windowsHydrateArgs(...args));
});
it("keeps WSL2 hydrate actions on the requested job", () => {
const args = ["--windows-mode", "wsl2", "--job", "hydrate", "--id", "cbx_existing"];
const { output } = runSuccessfulWindowsHydrate(...args);
expect(output.args).toEqual(windowsHydrateArgs(...args));
});
it("prefers Azure for unqualified Windows warmups", () => {
const { output } = runSuccessfulWrapper(azureProviderHelp, ["warmup", "--target", "windows"]);
expect(output.args).toEqual(["warmup", "--target", "windows", "--provider", "azure"]);
});
it("rejects Blacksmith Testbox for Windows-shaped proof", () => {
for (const args of [
["run", "--provider", "blacksmith-testbox", "--target", "windows", "--", "echo ok"],
[
"run",
"--provider",
"blacksmith-testbox",
"--target",
"windows",
"--windows-mode",
"wsl2",
"--",
"echo ok",
],
]) {
const result = runWrapper(azureProviderHelp, args);
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain(
"provider=blacksmith-testbox supports Linux Testbox proof only",
);
expect(result.stderr).toContain("windows-testbox-probe.yml");
}
});
it("fails closed for AWS proof when broker auth is missing", () => {
const result = runDefaultWrapper(["run", "--provider", "aws", "--", "echo ok"], {
configJson: { coordinator: "", brokerAuth: "missing" },
env: { OPENCLAW_FAKE_CRABBOX_MISSING_BROKER_PROVIDERS: "aws" },
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("provider=aws failed readiness for OpenClaw proof");
expect(result.stderr).toMatch(/recovery: run `\S+crabbox doctor --provider aws --json`/u);
});
it("fails closed for AWS proof when broker auth is stale", () => {
const result = runDefaultWrapper(["run", "--provider", "aws", "--", "echo ok"], {
configJson: { coordinator: "https://crabbox.openclaw.ai", brokerAuth: "configured" },
env: {
OPENCLAW_FAKE_CRABBOX_UNAUTHORIZED_PROVIDERS: "aws",
},
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain(
"provider=aws requires managed Crabbox broker authentication for OpenClaw proof",
);
expect(result.stderr).toContain("login --url https://crabbox.openclaw.ai");
});
it("ignores the legacy direct AWS override", () => {
const result = runDefaultWrapper(["run", "--provider", "aws", "--", "echo ok"], {
configJson: { coordinator: "", brokerAuth: "missing" },
env: {
OPENCLAW_CRABBOX_ALLOW_DIRECT_AWS: "1",
OPENCLAW_FAKE_CRABBOX_MISSING_BROKER_PROVIDERS: "aws",
},
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("provider=aws failed readiness for OpenClaw proof");
});
it("defaults AWS macOS warmups to on-demand capacity", () => {
const result = runDefaultWrapper(["warmup", "--provider", "aws", "--target", "macos"]);
expect(result.status).toBe(0);
expect(parseFakeCrabboxOutput(result).args).toEqual([
"warmup",
"--provider",
"aws",
"--target",
"macos",
"--market",
"on-demand",
]);
});
it.each([
{ selection: "market", options: ["--target=macos", "--market", "spot"] },
{ selection: "lease", options: ["--target", "macos", "--id", "cbx_existing"] },
])("preserves the explicit AWS macOS $selection selection", ({ options }) => {
const { output } = runSuccessfulDefaultWrapper([
"run",
"--provider",
"aws",
...options,
"--",
"echo ok",
]);
expect(output.args).toEqual(["run", "--provider", "aws", ...options, "--", "echo ok"]);
});
it("bootstraps only Node for raw AWS macOS node commands", () => {
const { output, remoteCommand } = runSuccessfulMacosCommand(["node", "--version"]);
expect(output.args).toContain("--shell");
expect(remoteCommand).toContain("openclaw_crabbox_bootstrap_macos_js");
expect(remoteCommand).toContain("node-v${node_version}-darwin-${node_arch}.tar.gz");
expect(remoteCommand).toContain("node --version >&2 || return 1");
expect(remoteCommand).not.toContain("corepack enable");
expect(remoteCommand).not.toContain("pnpm --version >&2");
expect(remoteCommand).not.toContain(".openclaw-crabbox-changed-gate.bundle");
expectGroupedShellCommand(remoteCommand, "node --version");
});
it("preflights Swift 6.3 for raw AWS macOS Swift app builds", () => {
const { output, remoteCommand } = runSuccessfulMacosCommand([
"swift",
"build",
"--package-path",
"apps/macos",
"--product",
"OpenClaw",
]);
expect(output.args).toContain("--shell");
expect(remoteCommand).toContain("openclaw_crabbox_require_macos_swift_63");
expect(remoteCommand).toContain("/Applications/Xcode_26*.app");
expect(remoteCommand).toContain("/Applications/Xcode-26*.app");
expect(remoteCommand).toContain("/Applications/Xcode_2[7-9]*.app");
expect(remoteCommand).toContain('sudo xcode-select -s "$openclaw_developer"');
expect(remoteCommand).toContain("OpenClaw macOS app proof requires Swift tools 6.3+");
expect(remoteCommand).toContain("xcodebuild -version");
expect(remoteCommand).toContain("OpenClaw macOS app proof requires Xcode 26.4+");
expect(remoteCommand).not.toContain("openclaw_crabbox_bootstrap_macos_js");
expectGroupedShellCommand(
remoteCommand,
"swift build --package-path apps/macos --product OpenClaw",
);
});
it("preflights Swift and JS tooling for raw AWS macOS package scripts", () => {
expectMacosPackageCommand(
runSuccessfulMacosCommand(["pnpm", "mac:package"]),
"pnpm mac:package",
(remoteCommand) => {
expect(remoteCommand).toContain("OpenClaw macOS app proof requires Swift tools 6.3+");
expect(remoteCommand).toContain("OpenClaw macOS app proof requires Xcode 26.4+");
},
);
});
it("preserves sanitized env pnpm package commands when Swift preflight is needed", () => {
const { output, remoteCommand } = runSuccessfulMacosCommand([
"env",
"-i",
"pnpm",
"mac:package",
]);
expect(output.args).toContain("--shell");
expect(remoteCommand).toContain("openclaw_crabbox_bootstrap_macos_js");
expect(remoteCommand).toContain("openclaw_crabbox_require_macos_swift_63");
expectGroupedShellCommand(remoteCommand, "openclaw_crabbox_env -i pnpm mac:package");
});
it("preserves sanitized env package script commands when JS tooling is needed", () => {
expectMacosPackageCommand(
runSuccessfulMacosCommand(["env", "-i", "bash", "scripts/package-mac-app.sh"]),
"openclaw_crabbox_env -i bash scripts/package-mac-app.sh",
);
});
it("does not bootstrap JS tooling for env package scripts behind command", () => {
const { remoteCommand } = runSuccessfulMacosCommand([
"command",
"env",
"-i",
"PATH=/usr/bin:/bin",
"bash",
"scripts/package-mac-app.sh",
]);
expect(remoteCommand).not.toContain("openclaw_crabbox_bootstrap_macos_js");
});
it("does not bootstrap JS tooling for nested env package scripts that cannot be shimmed", () => {
for (const args of [
["--", "bash", "-lc", "env -i PATH=/usr/bin:/bin bash scripts/package-mac-app.sh"],
["--shell", "--", "bash -lc 'env -i PATH=/usr/bin:/bin bash scripts/package-mac-app.sh'"],
]) {
const { remoteCommand } = runSuccessfulDefaultWrapper([
"run",
"--provider",
"aws",
"--target",
"macos",
...args,
]);
expect(remoteCommand).not.toContain("openclaw_crabbox_bootstrap_macos_js");
expect(remoteCommand).toContain("openclaw_crabbox_require_macos_swift_63");
}
});
it("does not bootstrap Corepack for nested env pnpm commands that cannot be shimmed", () => {
const { remoteCommand } = runSuccessfulMacosShell(
"bash -lc 'env -i PATH=/usr/bin:/bin pnpm --version'",
);
expect(remoteCommand).not.toContain("openclaw_crabbox_bootstrap_macos_js");
expect(remoteCommand).toBe("bash -lc 'env -i PATH=/usr/bin:/bin pnpm --version'");
});
it.each([
[
"env -i bash scripts/package-mac-app.sh",
"openclaw_crabbox_env -i bash scripts/package-mac-app.sh",
],
[
"env -i PATH=$PATH bash scripts/package-mac-app.sh > out.log",
"openclaw_crabbox_env -i PATH=$PATH bash scripts/package-mac-app.sh > out.log",
],
[
"env -i bash scripts/package-mac-app.sh >out.log 2>&1",
"openclaw_crabbox_env -i bash scripts/package-mac-app.sh >out.log 2>&1",
],
[
"env -i bash scripts/package-mac-app.sh && echo done",
"openclaw_crabbox_env -i bash scripts/package-mac-app.sh && echo done",
],
[
"set -e; env -i bash scripts/package-mac-app.sh",
"set -e; openclaw_crabbox_env -i bash scripts/package-mac-app.sh",
],
[
"time env -i bash scripts/package-mac-app.sh",
"time openclaw_crabbox_env -i bash scripts/package-mac-app.sh",
],
[
"(env -i bash scripts/package-mac-app.sh)",
"(openclaw_crabbox_env -i bash scripts/package-mac-app.sh)",
],
[
"{ env -i bash scripts/package-mac-app.sh; }",
"{ openclaw_crabbox_env -i bash scripts/package-mac-app.sh; }",
],
[
"if true; then env -i bash scripts/package-mac-app.sh; fi",
"if true; then openclaw_crabbox_env -i bash scripts/package-mac-app.sh; fi",
],
[
"FOO=1 env -i bash scripts/package-mac-app.sh",
"FOO=1 openclaw_crabbox_env -i bash scripts/package-mac-app.sh",
],
[
"FOO= env -i bash scripts/package-mac-app.sh",
"FOO= openclaw_crabbox_env -i bash scripts/package-mac-app.sh",
],
[
"FOO='a b' env -i bash scripts/package-mac-app.sh",
"FOO='a b' openclaw_crabbox_env -i bash scripts/package-mac-app.sh",
],
[
"PATH=/usr/bin:/bin env -i bash scripts/package-mac-app.sh",
"PATH=/usr/bin:/bin openclaw_crabbox_env -i bash scripts/package-mac-app.sh",
],
])("preserves package-script shell syntax when rewriting %s", (command, expected) => {
expectMacosPackageCommand(runSuccessfulMacosShell(command), expected, (remoteCommand) => {
expect(remoteCommand).not.toContain("'>'");
expect(remoteCommand).toContain('export OPENCLAW_CRABBOX_BOOTSTRAP_PATH="$PATH";');
});
});
it("does not rewrite heredoc bodies when sanitizing env package scripts", () => {
const shellCommand = [
"env -i bash scripts/package-mac-app.sh",
"cat <<EOF",
"env -i bash scripts/package-mac-app.sh",
"EOF",
].join("\n");
const expectedCommand = [
"openclaw_crabbox_env -i bash scripts/package-mac-app.sh",
"cat <<EOF",
"env -i bash scripts/package-mac-app.sh",
"EOF",
].join("\n");
const run = runSuccessfulMacosShell(shellCommand);
expectMacosPackageCommand(run, expectedCommand);
expect(run.remoteCommand).not.toContain("cat <<EOF\nopenclaw_crabbox_env");
});
it.each([
{
name: "preflights Swift and JS tooling for raw AWS macOS shell-launched package scripts",
script: "scripts/package-mac-app.sh",
},
{
js: false,
name: "keeps raw AWS macOS build-and-run scripts Swift-only",
script: "scripts/build-and-run-mac.sh",
},
])("$name", ({ js = true, script }) => {
const { output, remoteCommand } = runSuccessfulMacosCommand(["bash", script]);
expect(output.args).toContain("--shell");
expect(remoteCommand).toContain("openclaw_crabbox_require_macos_swift_63");
if (js) {
expect(remoteCommand).toContain("openclaw_crabbox_bootstrap_macos_js");
expect(remoteCommand).toContain("pnpm --version >&2");
} else {
expect(remoteCommand).not.toContain("openclaw_crabbox_bootstrap_macos_js");
}
expectGroupedShellCommand(remoteCommand, `bash ${script}`);
});
it("does not preflight Swift for raw AWS macOS commands that only mention package scripts", () => {
const { output, remoteCommand } = runSuccessfulMacosCommand([
"echo",
"scripts/package-mac-app.sh",
]);
expect(remoteCommand).not.toContain("openclaw_crabbox_require_macos_swift_63");
expect(output.args).toEqual([
"run",
"--provider",
"aws",
"--target",
"macos",
"--market",
"on-demand",
"--",
"echo",
"scripts/package-mac-app.sh",
]);
});
it("normalizes inherited Linux UTF-8 locale names for raw AWS macOS bootstrap", () => {
const { remoteCommand } = runSuccessfulMacosCommand(["node", "--version"], {
env: {
LANG: "C.UTF-8",
LC_ALL: "C.UTF-8",
LC_CTYPE: "C.UTF-8",
},
});
expect(remoteCommand).toContain('macos_locale="${OPENCLAW_CRABBOX_MACOS_LOCALE:-en_US.UTF-8}"');
expect(remoteCommand).toContain(
'case "${LANG:-}" in C.UTF-8|C.utf8|c.UTF-8|c.utf8) export LANG="$macos_locale" ;; esac;',
);
expect(remoteCommand).toContain(
'case "${LC_ALL:-}" in C.UTF-8|C.utf8|c.UTF-8|c.utf8) export LC_ALL="$macos_locale" ;; esac;',
);
expect(remoteCommand).toContain(
'case "${LC_CTYPE:-}" in C.UTF-8|C.utf8|c.UTF-8|c.utf8) export LC_CTYPE="$macos_locale" ;; esac;',
);
expectGroupedShellCommand(remoteCommand, "node --version");
});
it("bootstraps Bun for raw AWS macOS bun commands", () => {
const { output, remoteCommand, result } = runSuccessfulMacosCommand(["bun", "--version"]);
expect(output.args).toContain("--shell");
expect(result.stderr).toContain("Node/Corepack/pnpm/Bun");
expect(remoteCommand).toContain("openclaw_crabbox_bootstrap_macos_js");
expect(remoteCommand).toContain("bun_version=1.4.2");
expect(remoteCommand).toContain('bun_root="$tool_root/bun-v${bun_version}"');
expect(remoteCommand).toContain(
'npm install --global --prefix "$bun_root" --fetch-timeout=120000 --fetch-retries=2 --fetch-retry-mintimeout=2000 --fetch-retry-maxtimeout=15000 "bun@${bun_version}"',
);
expect(remoteCommand).toContain("bun --version >&2 || return 1");
expect(remoteCommand).not.toContain("corepack enable");
expectGroupedShellCommand(remoteCommand, "bun --version");
});
it("bootstraps Bun for raw AWS macOS env-prefixed bun commands", () => {
const { remoteCommand } = runSuccessfulMacosCommand(["env", "-i", "bun", "--version"]);
expect(remoteCommand).toContain("bun --version >&2 || return 1");
expectGroupedShellCommand(remoteCommand, "openclaw_crabbox_env -i bun --version");
});
it.each([
{
command: ["/usr/bin/env", "pnpm", "--version"],
expectShell: true,
expectedCommand: "openclaw_crabbox_env pnpm --version",
includes: ['corepack enable --install-directory "$PNPM_HOME"'],
name: "bootstraps Corepack for raw AWS macOS env-prefixed pnpm commands",
},
{
command: ["env", "-i", "PATH=/usr/bin:/bin", "pnpm", "--version"],
excludes: ["export -f env openclaw_crabbox_env", 'env() { openclaw_crabbox_env "$@"; };'],
expectedCommand: "openclaw_crabbox_env -i PATH=/usr/bin:/bin pnpm --version",
includes: [
"openclaw_crabbox_env",
"PATH=${OPENCLAW_CRABBOX_BOOTSTRAP_PATH:-$PATH}:${1#PATH=}",
],
name: "bootstraps Corepack for raw AWS macOS env option pnpm commands",
},
{
command: ["env", "-u", "FOO", "-i", "PATH=/usr/bin:/bin", "pnpm", "--version"],
expectedCommand: "openclaw_crabbox_env -u FOO -i PATH=/usr/bin:/bin pnpm --version",
includes: ["-u|--unset|-C|--chdir)", "-i|--ignore-environment)"],
name: "bootstraps Corepack for raw AWS macOS env options before ignore-environment",
},
{
command: ["/usr/bin/env", "-i", "PATH=/usr/bin:/bin", "pnpm", "--version"],
expectShell: true,
expectedCommand: "openclaw_crabbox_env -i PATH=/usr/bin:/bin pnpm --version",
name: "bootstraps Corepack for raw AWS macOS absolute env ignore-environment commands",
},
{
command: ["/usr/bin/env", "-i", "pnpm", "--version"],
expectedCommand: "openclaw_crabbox_env -i pnpm --version",
includes: [
'if [ "$openclaw_env_ignore" = "1" ] && [ "$openclaw_env_path_seen" = "0" ]; then openclaw_env_args+=("PATH=${OPENCLAW_CRABBOX_BOOTSTRAP_PATH:-$PATH}"); fi;',
],
name: "injects the bootstrapped PATH for raw AWS macOS absolute env -i commands",
},
])("$name", ({ command, excludes = [], expectShell, expectedCommand, includes = [] }) => {
const { output, remoteCommand } = runSuccessfulMacosCommand(command);
if (expectShell) {
expect(output.args).toContain("--shell");
}
for (const snippet of [
"openclaw_crabbox_bootstrap_macos_js",
"pnpm --version >&2",
...includes,
]) {
expect(remoteCommand).toContain(snippet);
}
for (const snippet of excludes) {
expect(remoteCommand).not.toContain(snippet);
}
expectGroupedShellCommand(remoteCommand, expectedCommand);
});
it.each([
{
command: ["./tools/env", "-i", "pnpm", "--version"],
expected: "./tools/env -i pnpm --version",
},
{
command: ["command", "env", "-i", "PATH=/usr/bin:/bin", "pnpm", "--version"],
expected: "command env -i PATH=/usr/bin:/bin pnpm --version",
},
{
command: ["exec", "env", "-i", "PATH=/usr/bin:/bin", "pnpm", "--version"],
expected: "exec env -i PATH=/usr/bin:/bin pnpm --version",
},
{ command: ["env", "-i", "-S", "pnpm --version"], expected: "env -i -S 'pnpm --version'" },
])("keeps unshimmable env commands outside JS bootstrap: $expected", ({ command }) => {
const { output } = runSuccessfulMacosCommand(command);
expect(output.args.slice(output.args.indexOf("--") + 1)).toEqual(command);
expect(output.args).not.toContain("--shell");
});
it("bootstraps env commands behind command when they keep the inherited PATH", () => {
const { remoteCommand } = runSuccessfulMacosCommand([
"command",
"env",
"CI=1",
"pnpm",
"--version",
]);
expect(remoteCommand).toContain("openclaw_crabbox_bootstrap_macos_js");
expect(remoteCommand).toContain("pnpm --version >&2");
expectGroupedShellCommand(remoteCommand, "command env CI=1 pnpm --version");
});
it("does not shadow unrelated env calls in AWS macOS shell commands", () => {
const shellScript = "node --version; env -i PATH=/usr/bin:/bin printenv PATH";
const { remoteCommand } = runSuccessfulMacosShell(shellScript);
expect(remoteCommand).toContain("openclaw_crabbox_bootstrap_macos_js");
expect(remoteCommand).toContain("openclaw_crabbox_env");
expect(remoteCommand).not.toContain('env() { openclaw_crabbox_env "$@"; };');
expectGroupedShellCommand(remoteCommand, shellScript);
});
it("bootstraps Corepack for raw AWS macOS env split-string pnpm commands", () => {
const { remoteCommand } = runSuccessfulMacosCommand(["/usr/bin/env", "-S", "pnpm --version"]);
expect(remoteCommand).toContain("openclaw_crabbox_bootstrap_macos_js");
expect(remoteCommand).toContain("pnpm --version >&2");
expect(remoteCommand.indexOf("-S|--split-string|-S*|--split-string=*)")).toBeLessThan(
remoteCommand.indexOf("-[!-]*i*)"),
);
expectGroupedShellCommand(remoteCommand, "openclaw_crabbox_env -S 'pnpm --version'");
});
it.each([
{ provider: "aws", target: "macos", targetArgs: [] },
{ provider: "azure", target: "windows", targetArgs: ["--windows-mode", "wsl2"] },
])(
"prepares the capsule installer before a Node changed gate ($provider/$target)",
({ provider, target, targetArgs }) => {
const { output, remoteCommand: renderedCommand } = runSuccessfulWrapper(
azureProviderHelp,
[
"run",
"--provider",
provider,
"--target",
target,
...targetArgs,
"--",
"node",
"scripts/check-changed.mjs",
],
sparseChangedGateOptions,
);
const remoteCommand = normalizeShellLineEndings(output.scriptContent || renderedCommand);
const node = remoteCommand.indexOf("node --version >&2");
const shim = remoteCommand.indexOf('corepack enable --install-directory "$PNPM_HOME"');
const receiver = remoteCommand.indexOf("node -e");
const manager = remoteCommand.indexOf("pnpm --version >&2");
expect(node).toBeGreaterThanOrEqual(0);
expect(shim).toBeGreaterThan(node);
expect(receiver).toBeGreaterThan(shim);
expect(manager).toBeGreaterThan(receiver);
expect(remoteCommand).toContain("install-dependencies.sh");
const payload =
"openclaw_crabbox_env " + remoteChangedGateEnvPrefix + " node scripts/check-changed.mjs";
if (provider === "aws") {
expectGroupedShellCommand(remoteCommand, payload);
} else {
expect(remoteCommand).toContain("{ " + payload + "\n}");
}
},
);
it("bootstraps Corepack for AWS macOS node option changed-gate commands", () => {
const { remoteCommand } = runSuccessfulMacosCommand([
"node",
"--max-old-space-size",
"4096",
"--env-file-if-exists",
".env",
"--unhandled-rejections",
"strict",
"--trace-warnings",
"--import=tsx",
"scripts/check-changed.mjs",
]);
expectMacosJsBootstrap(
remoteCommand,
`openclaw_crabbox_env ${remoteChangedGateEnvPrefix} node --max-old-space-size 4096 --env-file-if-exists .env --unhandled-rejections strict --trace-warnings --import=tsx scripts/check-changed.mjs`,
);
});
it("does not treat node script arguments as changed-gate commands", () => {
const { remoteCommand } = runSuccessfulMacosCommand([
"node",
"--trace-warnings",
"scripts/other.mjs",
"scripts/check-changed.mjs",
]);
expect(remoteCommand).toContain("openclaw_crabbox_bootstrap_macos_js");
expect(remoteCommand).not.toContain("OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1");
expectGroupedShellCommand(
remoteCommand,
"node --trace-warnings scripts/other.mjs scripts/check-changed.mjs",
);
});
it("preserves shell commands when bootstrapping raw AWS macOS JavaScript commands", () => {
const { output, remoteCommand } = runSuccessfulMacosShell("pnpm check:changed");
expect(output.args.filter((arg) => arg === "--shell")).toHaveLength(1);
expectMacosJsBootstrap(remoteCommand, `${remoteChangedGateExport} pnpm check:changed`);
});
it("bootstraps raw AWS macOS shell scripts that set up before JavaScript commands", () => {
const shellScript = [
"set -euo pipefail",
'repo_tmp=$(node -e "console.log(require(\\"node:os\\").tmpdir())")',
"pnpm --version",
].join("\n");
const { output, remoteCommand } = runSuccessfulMacosShell(shellScript);
expect(output.args.filter((arg) => arg === "--shell")).toHaveLength(1);
expectMacosJsBootstrap(remoteCommand, shellScript);
});
it("bootstraps raw AWS macOS shell scripts with env-prefixed JavaScript commands", () => {
const shellScript = "/usr/bin/env CI=1 pnpm --version";
const { remoteCommand } = runSuccessfulMacosShell(shellScript);
expect(remoteCommand).toContain("openclaw_crabbox_bootstrap_macos_js");
expect(remoteCommand).toContain("pnpm --version >&2");
expectGroupedShellCommand(remoteCommand, shellScript);
});
it("bootstraps AWS macOS script-stdin runs before the uploaded script body", () => {
const script = ["set -euo pipefail", "node -v", "pnpm --version"].join("\n");
const { output, result } = runSuccessfulMacosScript(script);
expect(output.args).not.toContain("--script-stdin");
expect(output.args).toContain("--script");
expect(result.stderr).toContain(
"bootstrapping pinned user-local JavaScript tooling before the command",
);
expect(output.scriptContent).toContain("openclaw_crabbox_bootstrap_macos_js");
expect(output.scriptContent).toContain('if [ ! -d "$TMPDIR" ]; then mkdir -p "$TMPDIR"');
expect(output.scriptContent).toContain("openclaw_crabbox_bootstrap_macos_js || exit $?");
expect(output.scriptContent).toContain('corepack enable --install-directory "$PNPM_HOME"');
expect(output.scriptContent).toContain("pnpm --version >&2");
expect(output.scriptContent).toContain(`\n${script}`);
});
it("preserves AWS macOS script-stdin shebang payloads behind the bootstrap wrapper", () => {
const script = ["#!/usr/bin/env node", "console.log(process.version);"].join("\n");
const { output } = runSuccessfulMacosScript(script, ["--", "arg1"]);
expect(output.args).not.toContain("--script-stdin");
expect(output.args).toContain("--script");
expect(output.scriptContent).toContain("openclaw_crabbox_bootstrap_macos_js || exit $?");
expect(output.scriptContent).not.toContain("corepack enable");
expect(output.scriptContent).not.toContain("pnpm --version >&2");
expect(output.scriptContent).toContain("cat >\"$tmp_script\" <<'OPENCLAW_CRABBOX_SCRIPT_0'");
expect(output.scriptContent).toContain(`\n${script}\nOPENCLAW_CRABBOX_SCRIPT_0\n`);
expect(output.scriptContent).toContain('chmod 700 "$tmp_script" || exit $?');
expect(output.scriptContent).toContain('"$tmp_script" "$@"');
expect(output.args.at(-1)).toBe("arg1");
});
it("bootstraps AWS macOS script-stdin shell shebang bodies before the uploaded script", () => {
const script = [
"#!/usr/bin/env bash",
"set -euo pipefail",
"pnpm --version",
"bun --version",
].join("\n");
const { output } = runSuccessfulMacosScript(script);
expect(output.scriptContent).toContain("openclaw_crabbox_bootstrap_macos_js || exit $?");
expect(output.scriptContent).toContain('corepack enable --install-directory "$PNPM_HOME"');
expect(output.scriptContent).toContain("pnpm --version >&2");
expect(output.scriptContent).toContain("bun --version >&2 || return 1");
expect(output.scriptContent).toContain(`\n${script}\n`);
});
it("preflights Swift for AWS macOS script-stdin Swift builds", () => {
const script = [
"set -euo pipefail",
"swift build --package-path apps/macos --product OpenClaw",
].join("\n");
const { output } = runSuccessfulMacosScript(script);
expect(output.scriptContent).toContain("openclaw_crabbox_bootstrap_macos_js");
expect(output.scriptContent).toContain("openclaw_crabbox_require_macos_swift_63");
expect(output.scriptContent).toContain("openclaw_crabbox_require_macos_swift_63 || exit $?");
expect(output.scriptContent).toContain("OpenClaw macOS app proof requires Swift tools 6.3+");
expect(output.scriptContent).toContain("OpenClaw macOS app proof requires Xcode 26.4+");
expect(output.scriptContent).toContain(`\n${script}`);
});
it("preflights Swift and JS for AWS macOS script-stdin package scripts", () => {
const script = ["#!/usr/bin/env bash", "set -euo pipefail", "pnpm mac:package"].join("\n");
const { output } = runSuccessfulMacosScript(script);
expect(output.scriptContent).toContain("openclaw_crabbox_bootstrap_macos_js");
expect(output.scriptContent).toContain("pnpm --version >&2");
expect(output.scriptContent).toContain("openclaw_crabbox_require_macos_swift_63");
expect(output.scriptContent).toContain("openclaw_crabbox_require_macos_swift_63 || exit $?");
expect(output.scriptContent).toContain(`\n${script}\n`);
});
it("bootstraps Corepack for AWS macOS script-stdin env shebangs with option values", () => {
const script = ["#!/usr/bin/env -C /tmp -u OPENCLAW_FAKE_VAR pnpm", "--version"].join("\n");
const { output } = runSuccessfulMacosScript(script);
expect(output.scriptContent).toContain("openclaw_crabbox_bootstrap_macos_js || exit $?");
expect(output.scriptContent).toContain('corepack enable --install-directory "$PNPM_HOME"');
expect(output.scriptContent).toContain("pnpm --version >&2");
expect(output.scriptContent).toContain(`\n${script}\n`);
});
it("bootstraps Bun for AWS macOS script-stdin bun shebangs", () => {
const script = ["#!/usr/bin/env bun", "console.log(Bun.version);"].join("\n");
const { output } = runSuccessfulMacosScript(script);
expect(output.scriptContent).toContain("bun_version=1.4.2");
expect(output.scriptContent).toContain(
'npm install --global --prefix "$bun_root" --fetch-timeout=120000 --fetch-retries=2 --fetch-retry-mintimeout=2000 --fetch-retry-maxtimeout=15000 "bun@${bun_version}"',
);
expect(output.scriptContent).toContain("bun --version >&2 || return 1");
expect(output.scriptContent).not.toContain("corepack enable");
});
it("does not treat run option values as AWS macOS script-stdin flags", () => {
const result = runDefaultWrapper(
[
"run",
"--provider",
"aws",
"--target",
"macos",
"--label",
"--script-stdin",
"--",
"echo ok",
],
{ input: "node -v\n" },
);
const output = parseFakeCrabboxOutput(result);
expect(result.status).toBe(0);
expect(output.args).toContain("--label");
expect(output.args).toContain("--script-stdin");
expect(output.args).not.toContain("--script");
expect(output.scriptContent).toBe("");
});
it.each(
// prettier-ignore
[
["bootstraps raw AWS macOS shell scripts with setup inside command substitutions", "version=$(cd repo && pnpm --version)", undefined],
["bootstraps raw AWS macOS shell scripts with assignment-prefix command substitutions", "TOOL_ROOT=$(pwd) pnpm --version", undefined],
["bootstraps raw AWS macOS shell scripts with case branches inside command substitutions", 'version=$(case "$pm" in pnpm) pnpm --version ;; esac)', undefined],
["bootstraps raw AWS macOS shell scripts with grouped setup inside command substitutions", 'echo "$( (echo setup); pnpm --version )"', undefined],
["bootstraps raw AWS macOS shell scripts after comments and setup commands", ["# setup", "cd repo && pnpm --version"].join("\n"), undefined],
["bootstraps raw AWS macOS shell scripts after escaped newlines", "cd repo && \\\npnpm --version", undefined],
["bootstraps raw AWS macOS shell scripts with exec-prefixed JavaScript commands", "set -e; exec pnpm check:changed", `${remoteChangedGateExport} set -e; exec pnpm check:changed`],
["bootstraps raw AWS macOS shell scripts with time-prefixed JavaScript commands", "time -p node -e 'process.exit(0)'", undefined],
["bootstraps raw AWS macOS shell scripts with JavaScript control conditions", "if node -e 'process.exit(0)'; then echo ok; fi", undefined],
["bootstraps raw AWS macOS shell scripts with env-prefixed JavaScript control conditions", "if CI=1 pnpm --version; then echo ok; fi", undefined],
["bootstraps raw AWS macOS shell scripts with JavaScript pipeline stages", "echo '{}' | node -e 'process.stdin.resume()'", undefined],
["bootstraps raw AWS macOS shell scripts after background setup commands", "setup_task & pnpm --version", undefined],
["bootstraps raw AWS macOS shell scripts with JavaScript else branches", "if test -d node_modules; then echo cached; else pnpm --version; fi", undefined],
["bootstraps raw AWS macOS shell scripts with JavaScript case branches", 'case "$(uname -m)" in arm64|x64) pnpm --version ;; esac', undefined],
] as const,
)("%s", (_name, shellScript, expectedCommand) => {
const { remoteCommand } = runSuccessfulMacosShell(shellScript);
expectMacosJsBootstrap(remoteCommand, expectedCommand ?? shellScript);
});
it.each(
// prettier-ignore
[
["does not bootstrap raw AWS macOS shell scripts for JavaScript-named case labels", 'case "$packageManager" in pnpm) echo "$packageManager" ;; esac', undefined],
["does not bootstrap raw AWS macOS shell scripts that only mention JavaScript tools", 'echo "node and pnpm are documented here"', true],
["does not bootstrap raw AWS macOS shell scripts for quoted JavaScript tool mentions", 'echo "docs; pnpm --version"', undefined],
["does not bootstrap raw AWS macOS shell scripts for inline comment mentions", "echo ok # $(pnpm --version)", undefined],
["does not bootstrap raw AWS macOS shell scripts for reserved words in arguments", "echo then pnpm --version && echo use-case", undefined],
["does not bootstrap raw AWS macOS shell scripts for arithmetic expansion names", "node=1; echo $((node + 1))", undefined],
["does not bootstrap raw AWS macOS shell scripts for quoted assignment mentions", 'MSG="use pnpm here" printf "%s\\n" "$MSG"', undefined],
["does not bootstrap raw AWS macOS shell scripts for command lookup checks", "command -v pnpm", undefined],
["does not bootstrap raw AWS macOS shell scripts for timed command lookup checks", "/usr/bin/time -l command -v pnpm", undefined],
] as const,
)("%s", (_name, shellScript, expectSingleShell) => {
const { output, remoteCommand } = runSuccessfulMacosShell(shellScript);
if (expectSingleShell) {
expect(output.args.filter((arg) => arg === "--shell")).toHaveLength(1);
}
expect(remoteCommand).not.toContain("openclaw_crabbox_bootstrap_macos_js");
});
it("groups shell commands so fallbacks cannot mask AWS macOS bootstrap failures", () => {
const { remoteCommand } = runSuccessfulMacosShell("pnpm check:changed || true");
expectMacosJsBootstrap(remoteCommand, `${remoteChangedGateExport} pnpm check:changed || true`);
});
it("does not bootstrap non-macOS AWS JavaScript commands", () => {
const { output, remoteCommand } = runSuccessfulDefaultWrapper([
"run",
"--provider",
"aws",
"--target",
"linux",
"--",
"pnpm",
"--version",
]);
expect(remoteCommand).not.toContain("openclaw_crabbox_bootstrap_macos_js");
expect(output.args.slice(0, 5)).toEqual(["run", "--provider", "aws", "--target", "linux"]);
expect(remoteCommand).toContain(".openclaw-crabbox-changed-gate.bundle");
expect(remoteCommand).not.toContain("OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1");
expect(remoteCommand.endsWith("; pnpm --version")).toBe(true);
});
it.each([
{
provider: "aws",
command: ["corepack pnpm check:changed"],
shell: true,
expected: "corepack pnpm check:changed",
},
{
provider: "azure",
command: ["corepack pnpm check:changed"],
shell: true,
expected: "corepack pnpm check:changed",
},
{
provider: "aws",
command: ["pnpm", "--filter", "@openclaw/discord", "test"],
shell: false,
expected: "pnpm --filter '@openclaw/discord' test",
},
] as const)(
"restores hydrated modules for $provider Windows commands: $expected",
({ provider, command, shell, expected }) => {
const run = runSuccessfulWrapper("provider: hetzner, aws, azure, local-container\n", [
"run",
"--provider",
provider,
"--target",
"windows",
"--windows-mode",
"normal",
"--id",
"cbx_test",
...(shell ? ["--shell"] : []),
"--",
...command,
]);
expect(run.output.args).toContain("--shell");
expect(run.remoteCommand).toContain(
"$env:CRABBOX_PNPM_MODULES_DIR) { $env:CRABBOX_PNPM_MODULES_DIR } else { $env:PNPM_CONFIG_MODULES_DIR }",
);
expect(run.remoteCommand).toContain("hydrated pnpm modules directory does not exist");
expect(run.remoteCommand).toContain('mklink /J "$openclawSelfModules" "$openclawModulesDir"');
expect(run.remoteCommand).toContain(
'mklink /J "$openclawWorkspaceModules" "$openclawModulesDir"',
);
expect(run.remoteCommand).toContain(expected);
},
);
it.each([
{
source: "environment",
args: ["--provider", "aws"],
options: { env: { CRABBOX_TARGET: "windows" } },
},
{
source: "config",
args: [],
options: {
configJson: managedBrokerConfig("aws", { target: "windows", windowsMode: "normal" }),
},
},
])("preserves $source-selected native Windows argv", ({ args, options }) => {
const { output } = runSuccessfulDefaultWrapper(["run", ...args, "--", "echo", "ok"], options);
expect(output.args).not.toContain("--shell");
expect(output.args.slice(-2)).toEqual(["echo", "ok"]);
});
it("keeps env-selected WSL2 runs on the POSIX bootstrap path", () => {
const { output } = runSuccessfulDefaultWrapper(
["run", "--provider", "aws", "--", "corepack", "pnpm", "check:changed"],
{
env: {
CRABBOX_TARGET: "windows",
CRABBOX_WINDOWS_MODE: "wsl2",
},
},
);
expect(output.args).toContain("--script");
expect(output.args).not.toContain("--shell");
expect(output.scriptContent).toContain("openclaw_crabbox_bootstrap_wsl2_js");
});
const itWithPosixLinkedWorktreeFixture = process.platform === "win32" ? it.skip : it;
itWithPosixLinkedWorktreeFixture(
"finds a Crabbox checkout next to the Git common dir in linked worktrees",
() => {
const fakeWorkspaceParent = mkdtempSync(path.join(tmpdir(), "openclaw-linked-worktree-"));
tempDirs.push(fakeWorkspaceParent);
const gitCommonDir = path.join(fakeWorkspaceParent, "openclaw", ".git");
const crabboxBinDir = path.join(fakeWorkspaceParent, "crabbox", "bin");
mkdirSync(gitCommonDir, { recursive: true });
writeFakeCrabbox(crabboxBinDir, "provider: aws\n");
const gitResponses = {
[GIT_COMMON_DIR_KEY]: { stdout: `${gitCommonDir}\n` },
};
const gitBinDir = makeFakeGit(gitResponses);
const nodeExecPath = resolveTestNodeExecPath();
const result = spawnSync(
nodeExecPath,
["scripts/crabbox-wrapper.mjs", "run", "--provider", "aws", "--no-sync", "--", "echo ok"],
{
cwd: repoRoot,
encoding: "utf8",
env: {
...process.env,
OPENCLAW_CRABBOX_WRAPPER_IGNORE_REPO_BINARY: "1",
OPENCLAW_FAKE_GIT_RESPONSES: JSON.stringify(gitResponses),
PATH: [gitBinDir, path.dirname(nodeExecPath)].join(path.delimiter),
},
},
);
expect(result.status).toBe(0);
expect(parseFakeCrabboxOutput(result).args).toContain("aws");
},
);
it("accepts advertised providers from wrapped Crabbox help", () => {
const result = runWrapper(
[
"provider: hetzner, aws, local-container, blacksmith-testbox,",
" docker, or cloudflare (default: aws)",
"",
].join("\n"),
["run", "--provider", "docker", "--", "echo ok"],
);
expect(result.status).toBe(0);
expect(parseFakeCrabboxOutput(result).args).toContain("docker");
expect(result.stderr).toContain(
"providers=hetzner,aws,local-container,blacksmith-testbox,docker,cloudflare",
);
});
if (process.platform === "win32") {
it("preserves shell metacharacters through Windows Crabbox command shims", () => {
const remoteCommand = "pnpm build && pnpm test | more < in.txt > out.txt %PATH%";
const result = runWrapper("provider: aws\n", [
"run",
"--provider",
"aws",
"--shell",
"--",
remoteCommand,
]);
expect(result.status).toBe(0);
expect(parseFakeCrabboxOutput(result).args).toEqual([
"run",
"--provider",
"aws",
"--shell",
"--",
remoteCommand,
]);
});
}
if (process.platform !== "win32") {
it("keeps POSIX PATH lookup semantics for non-executable entries", () => {
const staleBinDir = mkdtempSync(path.join(tmpdir(), "openclaw-stale-crabbox-"));
tempDirs.push(staleBinDir);
writeFileSync(path.join(staleBinDir, "crabbox"), "not executable\n", "utf8");
const result = runWrapper("provider: aws\n", ["run", "--provider", "aws", "--", "echo ok"], {
extraPathEntries: [staleBinDir],
});
expect(result.status).toBe(0);
expect(parseFakeCrabboxOutput(result).args).toContain("aws");
});
}
it("falls back to normal sync decisions when git is missing from PATH", () => {
const result = runDefaultWrapper(["run", "--provider", "aws", "--", "echo ok"], {
gitResponses: {
[GIT_COMMON_DIR_KEY]: { status: 1 },
[GIT_CONFIG_SPARSE_KEY]: { status: 1 },
[GIT_SPARSE_LIST_KEY]: { status: 1 },
},
});
expect(result.status).toBe(0);
expect(parseFakeCrabboxOutput(result).args).toContain("aws");
});
it.each([false, true])("requires advertised Tensorlake support for aliases: %s", (advertised) => {
const providers = parseProvidersFromHelp(`provider: aws${advertised ? ", tensorlake" : ""}\n`);
for (const provider of ["tensorlake", "tl", "tensorlake-sbx"]) {
expect(isProviderAdvertised(provider, providers), provider).toBe(advertised);
}
});
it.each([
["run", "--help"],
["warmup", "--help"],
["actions", "hydrate", "--help"],
["warmup", "--keep", "--help"],
["actions", "hydrate", "--reclaim", "--help"],
["help", "actions", "hydrate"],
["run", "--label", "--", "--help"],
["warmup", "--lease-id", "--", "--help"],
["actions", "hydrate", "--field", "--", "--help"],
])("prints help without provider checks or sparse checkout preparation: %j", (...args) => {
const logPath = makeInvocationLog();
writeFileSync(logPath, "");
const syncRoot = path.join(path.dirname(logPath), "sync");
const result = runDefaultWrapper(args, {
...cleanSparseSyncOptions,
configJson: managedBrokerConfig("aws"),
env: {
OPENCLAW_CRABBOX_SYNC_TMPDIR: syncRoot,
OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG: logPath,
},
});
expect(result.status).toBe(0);
const commandLength = args[0] === "actions" ? 2 : 1;
if (args.length === commandLength + 1) {
const optionHelp =
args[0] === "run"
? fakeRunValueOptionHelp
: args[0] === "warmup"
? fakeWarmupValueOptionHelp
: fakeHydrateValueOptionHelp;
expect(result.stdout).toBe(`${defaultProviderHelp}${optionHelp}`);
} else {
expect(parseFakeCrabboxOutput(result).args).toEqual(args);
}
expect(existsSync(syncRoot)).toBe(false);
expect(
readInvocations(logPath).filter(([command]) => command === "config" || command === "doctor"),
).toEqual([]);
});
it.each([
["run", "--provider", "aws", "--label", "--help", "--", "echo ok"],
["run", "--provider", "aws", "--", "--help"],
["run", "--provider", "aws", "node", "--help"],
["warmup", "--", "--help"],
["actions", "hydrate", "--", "--help"],
["warmup", "--lease-id", "--help"],
["actions", "hydrate", "--field", "--help"],
["run", "--provider", "aws", "-", "--help"],
["actions", "hydrate", "--provider", "aws", "-", "--help"],
])("keeps provider gates when help belongs to a payload: %j", (...args) => {
const result = runDefaultWrapper(args, {
configJson: directBrokerConfig("aws"),
env: { OPENCLAW_FAKE_CRABBOX_MISSING_BROKER_PROVIDERS: "aws" },
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("provider=aws failed readiness for OpenClaw proof");
});
it("keeps unsupported provider selections rejected", () => {
const result = runDefaultWrapper(["run", "--provider", "bogus", "--", "echo ok"]);
expect(result.status).toBe(2);
expect(result.stderr).toContain("selected binary does not advertise provider bogus");
});
it.each([
["help", "run", "--", "echo ok"],
["help", "actions", "hydrate", "--", "echo ok"],
])("does not bypass preparation for a help alias containing a remote payload: %j", (...args) => {
const result = runDefaultWrapper(args, {
configJson: managedBrokerConfig("bogus"),
});
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("selected binary does not advertise provider bogus");
});
it("retries a cold Crabbox whose run --help is slower than the default probe timeout", () => {
const helpText = "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n";
// First probe is SIGKILLed at 25ms; the retry gets the full generous timeout
// and reads the (80ms) stderr help, so the wrapper must not hard-fail.
const result = runWrapper(helpText, ["--version"], {
env: { OPENCLAW_TEST_CRABBOX_METADATA_PROBE_TIMEOUT_MS: "25" },
extraPathEntries: [makeSlowHelpCrabbox(helpText, 80)],
});
expect(result.error).toBeUndefined();
expect(result.status).toBe(0);
expect(result.stderr).not.toContain("could not parse provider list");
expect(result.stderr).not.toContain("selected binary failed --help sanity checks");
expect(result.stderr).toContain(
"providers=hetzner,aws,local-container,blacksmith-testbox,cloudflare",
);
});
it("parses provider choices from the supported --provider help formats", () => {
const helpText =
"Usage: crabbox run [options]\n --provider hetzner|aws|local-container|blacksmith-testbox|cloudflare\n";
expect(parseProvidersFromHelp(helpText)).toEqual([
"hetzner",
"aws",
"local-container",
"blacksmith-testbox",
"cloudflare",
]);
expect(
parseProvidersFromHelp(
" -provider string\n provider: aws, blacksmith-testbox, local-container (defaults to configured selection)\n",
),
).toEqual(["aws", "blacksmith-testbox", "local-container"]);
});
it.each([
{
scenario: "Blacksmith feature ref",
provider: "blacksmith-testbox",
args: ["--blacksmith-ref", "feature-branch"],
command: ["corepack", "pnpm", "check:changed"],
},
{
scenario: "AWS changed gate",
provider: "aws",
args: [],
command: ["corepack", "pnpm", "check:changed"],
overlay: true,
},
{
scenario: "AWS Windows lease",
provider: "aws",
args: ["--target", "windows", "--id", "cbx_existing"],
command: ["corepack", "pnpm", "build"],
},
{ scenario: "local container", provider: "local-container", args: [], command: ["echo ok"] },
{
scenario: "existing AWS lease",
provider: "aws",
args: ["--id", "cbx_existing"],
command: ["echo ok"],
reclaim: true,
},
{
scenario: "Blacksmith main ref",
provider: "blacksmith-testbox",
args: ["--blacksmith-ref", "main"],
command: ["echo ok"],
},
])(
"syncs a clean sparse checkout through a full worktree: $scenario",
({ provider, args, command, overlay, reclaim }) => {
const { output, result } = runSuccessfulDefaultWrapper(
["run", "--provider", provider, ...args, "--", ...command],
cleanSparseSyncOptions,
);
expect(result.stderr).toContain("syncing from temporary full checkout");
expect(output.cwd).toContain("openclaw-crabbox-sync-");
expect(output.args).not.toContain("--no-sync");
if (overlay) {
expect(result.stderr).toContain("overlaying the local worktree as changes from abc123");
expect(output.args.join(" ")).toContain(".openclaw-crabbox-changed-gate.bundle");
}
if (reclaim) {
expect(output.args).toContain("--reclaim");
}
},
);
it("bootstraps Git metadata for sparse changed gates on remote raw syncs", () => {
const { output, remoteCommand } = runSuccessfulDefaultWrapper(
["run", "--provider", "aws", "--", "corepack", "pnpm", "check:changed"],
sparseChangedGateOptions,
);
expect(output.args).toContain("--shell");
expectChangedGateGitBootstrap(remoteCommand);
expect(remoteCommand).toContain("refs/openclaw/source-capsule");
expect(remoteCommand).toMatch(
/; env OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 CI=1 corepack pnpm check:changed$/u,
);
});
it("uses an explicit release base for changed-gate sync and remote Git metadata", () => {
const { remoteCommand, result } = runSuccessfulDefaultWrapper(
[
"run",
"--provider",
"aws",
"--",
"corepack",
"pnpm",
"check:changed",
"--base",
"origin/release/2026.7.2",
"--head",
"HEAD",
],
{
env: { OPENCLAW_FAKE_GIT_BASE_SHA: "release123" },
gitResponses: {
[GIT_CONFIG_SPARSE_KEY]: { stdout: "true\n" },
[GIT_STATUS_PORCELAIN_KEY]: { stdout: "" },
[GIT_CHECK_RELEASE_REF_KEY]: { stdout: "" },
[GIT_MERGE_BASE_RELEASE_HEAD_KEY]: { stdout: "release123\n" },
},
},
);
expect(result.stderr).toContain("overlaying the local worktree as changes from release123");
expect(remoteCommand).toContain('"baseSha":"release123"');
expect(remoteCommand).toContain("refs/remotes/origin/release/2026.7.2");
expect(remoteCommand).toContain(
"corepack pnpm check:changed --base origin/release/2026.7.2 --head HEAD",
);
});
it("rejects changed-gate revision expressions that cannot be recreated remotely", () => {
const result = runDefaultWrapper(
[
"run",
"--provider",
"aws",
"--",
"corepack",
"pnpm",
"check:changed",
"--base",
"origin/main~1",
],
{
gitResponses: {
[GIT_CONFIG_SPARSE_KEY]: { stdout: "true\n" },
[GIT_STATUS_PORCELAIN_KEY]: { stdout: "" },
},
},
);
expect(result.status).not.toBe(0);
expect(result.stderr).toContain(
"remote changed-gate sync requires an exact origin/<branch> base; received: origin/main~1",
);
});
it("rejects compound changed gates with incompatible bases", () => {
const result = runDefaultWrapper(
[
"run",
"--provider",
"aws",
"--shell",
"--",
"pnpm check:changed --base origin/release/2026.7.2 && pnpm check:changed --base origin/hotfix",
],
{
gitResponses: {
[GIT_CONFIG_SPARSE_KEY]: { stdout: "true\n" },
[GIT_STATUS_PORCELAIN_KEY]: { stdout: "" },
},
},
);
expect(result.status).not.toBe(0);
expect(result.stderr).toContain(
"remote changed-gate sync requires one base; received: origin/release/2026.7.2, origin/hotfix",
);
});
it.skipIf(process.platform === "win32").each([
["mkdir", "mkdir", "mkdirSync", "EACCES", -13],
["tagged write", "write file", "writeSync", "UNKNOWN", -122],
["directory open", "write file", "openSync", "EISDIR", -21],
["readonly write", "write file", "writeSync", "EBADF", -9],
["symlink", "symlink", "symlinkSync", "EPERM", -1],
["symlink blob", "write symlink blob", "writeFileSync", "ENOSPC", -28],
["chmod", "chmod", "chmodSync", "EPERM", -1],
["short writes", "copy", "writeSync", undefined, undefined],
["read after prefix", "copy", "readSync", "EIO", -5],
["unknown read", "copy", "readSync", "UNKNOWN", -122],
["growth lookalike", "copy", "readSync", "too-large", undefined],
["helper lookalike", "copy", "readSync", "helper-failed", undefined],
["growth", "source change", "readSync", "too-large", undefined],
["zero write", "write file", "writeSync", "helper-failed", undefined],
["early EOF", "source change", "readSync", undefined, undefined],
] as const)(
"handles source capsule %s without publishing partial source",
(fault, operation, method, code, errno) => {
const root = invocationLogTempDirs.make("openclaw-capsule-write-failure-");
const producer = path.join(root, "producer");
const syncRoot = path.join(root, "sync");
const invocationLog = path.join(root, "invocations.jsonl");
const capturedBundle = path.join(root, "captured.bundle");
const sourcePath = 'nested/diagnostic"line\n.txt';
const link = operation.includes("symlink");
const env = {
...testHomeEnv(path.join(root, "home")),
PATH: [
makeFakeCrabbox(defaultProviderHelp),
path.dirname(process.execPath),
process.env.PATH ?? "",
].join(path.delimiter),
GIT_CONFIG_GLOBAL: "/dev/null",
GIT_CONFIG_NOSYSTEM: "1",
GIT_AUTHOR_NAME: "Capsule fixture",
GIT_AUTHOR_EMAIL: "capsule@example.invalid",
GIT_COMMITTER_NAME: "Capsule fixture",
GIT_COMMITTER_EMAIL: "capsule@example.invalid",
OPENCLAW_CRABBOX_WRAPPER_IGNORE_REPO_BINARY: "1",
OPENCLAW_CRABBOX_SYNC_TMPDIR: syncRoot,
OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES: "0",
OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG: invocationLog,
OPENCLAW_FAKE_CRABBOX_COPY_CHANGED_GATE_BUNDLE_TO: capturedBundle,
};
mkdirSync(producer);
const git = (args: string[]) => {
const result = spawnSync("git", args, {
cwd: producer,
env,
encoding: "utf8",
timeout: 10_000,
});
expect(result.status, result.stderr).toBe(0);
return result.stdout;
};
git(["init", "-q", "-b", "main", "--template="]);
git(["remote", "add", "origin", producer]);
writeFileSync(path.join(producer, ".gitignore"), ".tmp/\n");
mkdirSync(path.join(producer, "nested"));
writeFileSync(path.join(producer, "data.txt"), "source bytes\n");
if (link) {
symlinkSync("../data.txt", path.join(producer, sourcePath));
} else {
writeFileSync(path.join(producer, sourcePath), "executable source bytes\n", {
mode: 0o755,
});
}
git(["add", "-A"]);
git(["commit", "-qm", "base"]);
const head = git(["rev-parse", "HEAD"]);
git(["update-ref", "refs/remotes/origin/main", head.trim()]);
const index = git(["ls-files", "--stage", "-z"]);
const sourceBytes = link
? readlinkSync(path.join(producer, sourcePath), { encoding: "buffer" })
: readFileSync(path.join(producer, sourcePath));
const sourceMode = lstatSync(path.join(producer, sourcePath)).mode;
const wrapper = path.join(producer, ".tmp", "wrapper.mjs");
mkdirSync(path.dirname(wrapper));
copyFileSync(realBundledWrapperPath, wrapper);
const preload = path.join(root, "write-failure.cjs");
writeFileSync(
preload,
`
const fs = require("node:fs");
const path = require("node:path");
const { syncBuiltinESMExports } = require("node:module");
const fault = ${JSON.stringify(fault)};
const operation = ${JSON.stringify(operation)};
const method = ${JSON.stringify(method)};
const sourcePath = ${JSON.stringify(sourcePath)};
const source = ${JSON.stringify(path.join(producer, sourcePath))};
const originalOpen = fs.openSync, originalClose = fs.closeSync;
const originalReadFile = fs.readFileSync;
const originalRead = fs.readSync, originalWrite = fs.writeSync;
const original = fs[method];
const owned = new Set();
let sourceFd, targetFd, target, failure, diagnostic, opened = 0, closed = 0;
let writes = 0, written = 0, targetBytes, grew = false;
const selectedTarget = (file) => file.startsWith(${JSON.stringify(syncRoot + path.sep)}) &&
file.endsWith(path.join("source", sourcePath));
fs.openSync = (file, ...args) => {
if (fault === "directory open" && selectedTarget(String(file))) {
try { return originalOpen(${JSON.stringify(syncRoot)}, "w"); }
catch (error) { failure = error; throw error; }
}
const fd = originalOpen(file, ...args);
if (file === source || selectedTarget(String(file))) {
owned.add(fd); opened++;
if (file === source) sourceFd = fd;
else { targetFd = fd; target = String(file); }
}
return fd;
};
fs.closeSync = (fd) => {
if (fd === targetFd) {
const inspectionFd = originalOpen(target, "r");
try { targetBytes = originalReadFile(inspectionFd).toString("base64"); }
finally { originalClose(inspectionFd); }
}
const result = originalClose(fd);
if (owned.delete(fd)) closed++;
if (fd === sourceFd) sourceFd = undefined;
if (fd === targetFd) targetFd = undefined;
return result;
};
fs.readFileSync = (file, ...args) => {
if (fault === "short writes" && file === sourceFd)
throw new Error("fixture source copy must use bounded reads");
return originalReadFile(file, ...args);
};
fs.readSync = (fd, buffer, offset, length, position) => {
if (fd === sourceFd) {
if (fault === "growth" && written > 0 && !grew) {
const growthFd = originalOpen(source, "a");
try { originalWrite(growthFd, Buffer.from("x"), 0, 1, null); }
finally { originalClose(growthFd); }
grew = true;
}
if (fault === "early EOF") {
if (written > 0) return 0;
length = Math.min(length, 3);
}
if (fault === "read after prefix" && written === 0)
length = Math.min(length, 3);
if ((fault === "read after prefix" && written > 0) ||
["unknown read", "growth lookalike", "helper lookalike"].includes(fault)) {
failure = Object.assign(new Error("fixture filesystem failure"), {
code: ${JSON.stringify(code)}, errno: ${errno},
...(fault === "read after prefix" ? { syscall: "read" } : {}),
...(fault.endsWith("lookalike") ? { name: "FsSafeError" } : {})
});
throw failure;
}
}
return originalRead(fd, buffer, offset, length, position);
};
fs.writeSync = (fd, buffer, offset, length, position) => {
if (fd === targetFd) {
writes++;
if (fault === "readonly write") {
try { return originalWrite(sourceFd, buffer, offset, length, position); }
catch (error) { failure = error; throw error; }
}
if (fault === "tagged write") {
failure = Object.assign(new Error("fixture filesystem failure"), {
code: "UNKNOWN", errno: -122, syscall: "write"
});
throw failure;
}
if (fault === "zero write") return 0;
const count = originalWrite(fd, buffer, offset,
fault === "short writes" ? Math.min(length, 3) : length, position);
written += count;
return count;
}
return originalWrite(fd, buffer, offset, length, position);
};
if (!["openSync", "readSync", "writeSync"].includes(method)) fs[method] = (...args) => {
const file = String(args[method === "symlinkSync" ? 1 : 0]);
const selected = operation === "write symlink blob"
? path.dirname(file).endsWith(path.sep + "links")
: file.endsWith(path.join("source", operation === "mkdir" ? path.dirname(sourcePath) : sourcePath));
if (file.startsWith(${JSON.stringify(syncRoot + path.sep)}) && selected) {
failure = Object.assign(new Error("fixture filesystem failure"), { code: ${JSON.stringify(code)}, errno: ${errno} });
throw failure;
}
return original(...args);
};
syncBuiltinESMExports();
process.on("uncaughtExceptionMonitor", (error) => {
const underlying = failure ?? error.cause ?? error;
diagnostic = {
message: error.message, code: underlying.code, errno: underlying.errno, syscall: underlying.syscall,
sameError: error === failure, causeIsOriginal: failure !== undefined && error.cause === failure,
cause: error.cause && { name: error.cause.name, code: error.cause.code }
};
});
process.on("exit", () => {
process.stderr.write("CAPSULE_IO " + JSON.stringify({
diagnostic, opened, closed, outstanding: owned.size, writes, written, targetBytes, grew
}) + "\\n");
});
`,
);
const result = spawnSync(
process.execPath,
[
"--require",
preload,
wrapper,
"run",
"--provider",
"aws",
"--target",
"linux",
"--",
"pnpm",
"check:changed",
],
{ cwd: producer, env, encoding: "utf8", timeout: 10_000 },
);
expect(result.error).toBeUndefined();
const record = result.stderr.split("\n").find((line) => line.startsWith("CAPSULE_IO "));
expect(record, result.stderr).toBeDefined();
const observed = JSON.parse(record!.slice("CAPSULE_IO ".length));
expect(observed.outstanding).toBe(0);
expect(observed.closed).toBe(observed.opened);
if (!link && operation !== "mkdir" && fault !== "short writes") {
expect(observed.opened).toBe(fault === "directory open" ? 1 : 2);
}
if (fault === "short writes") {
expect(observed.diagnostic?.message).not.toBe("fixture source copy must use bounded reads");
expectSuccessfulWrapperRun(result);
expect(observed.diagnostic).toBeUndefined();
expect(observed.opened).toBe(2);
expect(observed.writes).toBeGreaterThan(1);
expect(observed.written).toBe(sourceBytes.length);
expect(Buffer.from(observed.targetBytes, "base64")).toEqual(sourceBytes);
expect(existsSync(capturedBundle)).toBe(true);
expect(readdirSync(syncRoot)).toEqual([]);
} else {
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(
readInvocations(invocationLog).some(
(args) => args[0] === "sync-plan" || (args[0] === "run" && args[1] !== "--help"),
),
).toBe(false);
expect(existsSync(capturedBundle)).toBe(false);
expect(readdirSync(syncRoot)).toEqual([]);
if (fault === "growth") {
expect(observed.grew).toBe(true);
expect(readFileSync(path.join(producer, sourcePath))).toEqual(
Buffer.concat([sourceBytes, Buffer.from("x")]),
);
// Restore only this deliberate fixture mutation after the child has joined.
writeFileSync(path.join(producer, sourcePath), sourceBytes);
}
}
expect(git(["rev-parse", "HEAD"])).toBe(head);
expect(git(["ls-files", "--stage", "-z"])).toBe(index);
expect(git(["status", "--porcelain=v1", "-z"])).toBe("");
expect(lstatSync(path.join(producer, sourcePath)).mode).toBe(sourceMode);
expect(
link
? readlinkSync(path.join(producer, sourcePath), { encoding: "buffer" })
: readFileSync(path.join(producer, sourcePath)),
).toEqual(sourceBytes);
if (fault === "short writes") {
return;
}
const diagnostic = observed.diagnostic;
expect(diagnostic).toBeDefined();
expect(diagnostic.code).toBe(code);
expect(diagnostic.errno).toBe(errno);
if (fault === "read after prefix" || fault === "early EOF") {
expect(observed.written).toBe(3);
expect(Buffer.from(observed.targetBytes, "base64")).toEqual(sourceBytes.subarray(0, 3));
}
if (operation === "copy") {
expect(diagnostic.sameError).toBe(true);
expect(diagnostic.causeIsOriginal).toBe(false);
expect(diagnostic.message).toBe("fixture filesystem failure");
return;
}
if (operation === "source change") {
expect(diagnostic.message).toBe(
`source changed while freezing ${JSON.stringify(sourcePath)}; retry after edits finish`,
);
expect(diagnostic.cause).toEqual(
fault === "growth" ? { name: "FsSafeError", code: "too-large" } : undefined,
);
return;
}
expect(diagnostic.message).toContain(
`source capsule: ${operation} failed for ${JSON.stringify(sourcePath)}`,
);
if (fault === "zero write") {
expect(diagnostic.message).toContain('code="helper-failed"');
expect(diagnostic.cause).toEqual({ name: "FsSafeError", code: "helper-failed" });
} else {
expect(diagnostic.message).toContain(`code=${JSON.stringify(code)}, errno=${errno}`);
expect(diagnostic.causeIsOriginal).toBe(true);
}
if (fault === "directory open") {
expect(diagnostic.syscall).toBe("open");
}
if (fault === "readonly write" || fault === "tagged write") {
expect(diagnostic.syscall).toBe("write");
}
},
);
it.skipIf(process.platform === "win32").each([
["aws", true, "transport"],
["aws", false, "transport"],
["aws", false, "arbitrary"],
["blacksmith-testbox", true, "transport"],
["blacksmith-testbox", false, "transport"],
["blacksmith-testbox", false, "graph"],
["blacksmith-testbox", false, "frozen"],
["blacksmith-testbox", false, "lifecycle"],
["blacksmith-testbox", false, "lifecycle-staged-extra"],
["blacksmith-testbox", false, "lifecycle-base-ref"],
] as const)(
"transports verified source and dependencies (provider=%s, shallow=%s, scenario=%s)",
(provider, shallow, scenario) => {
const root = makeTempDir(tempDirs, "openclaw-changed-gate-real-git-");
const origin = path.join(root, "origin");
const producer = path.join(root, "producer");
const capturedBundle = path.join(root, "captured.bundle");
const deletedOwned = [
"committed-deleted.ignored",
"staged-deleted.ignored",
"index-only-deleted.ignored",
"deleted-dir.ignored/owned.txt",
];
const retainedPrivate = ["protected-deleted.ignored", "runtime.ignored"];
const replacedHistory = provider === "aws" && shallow;
const deletionReferent = path.join(root, "deletion-referent");
mkdirSync(deletionReferent);
writeFileSync(path.join(deletionReferent, "canary.txt"), "private referent\n");
const fakeBin = makeFakeCrabbox(defaultProviderHelp);
const home = path.join(root, "home");
const nodeExecPath = resolveTestNodeExecPath();
const env = {
...testHomeEnv(home),
PATH: [fakeBin, path.dirname(nodeExecPath), process.env.PATH ?? ""].join(path.delimiter),
GIT_CONFIG_GLOBAL: "/dev/null",
GIT_CONFIG_NOSYSTEM: "1",
GIT_OPTIONAL_LOCKS: "0",
GIT_AUTHOR_NAME: "Transport fixture",
GIT_AUTHOR_EMAIL: "transport@example.invalid",
GIT_COMMITTER_NAME: "Transport fixture",
GIT_COMMITTER_EMAIL: "transport@example.invalid",
OPENCLAW_CRABBOX_WRAPPER_IGNORE_REPO_BINARY: "1",
OPENCLAW_CRABBOX_SYNC_TMPDIR: path.join(root, "sync"),
OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES: "0",
OPENCLAW_FAKE_CRABBOX_COPY_CHANGED_GATE_BUNDLE_TO: capturedBundle,
OPENCLAW_FAKE_CRABBOX_NATIVE_SYNC_CAPTURE:
provider === "blacksmith-testbox" ? path.join(root, "native-sync.json") : "",
OPENCLAW_FAKE_CRABBOX_PRIVACY_PATHS: JSON.stringify([
"private-canary.txt",
"protected-deleted.ignored",
]),
};
const runCommand = (
command: string,
args: string[],
options: SpawnSyncOptionsWithStringEncoding,
) => {
const startedAt = Date.now();
const result = spawnSync(command, args, options);
return { ...result, elapsedMs: Date.now() - startedAt };
};
const failureDetail = (result: ReturnType<typeof runCommand>) =>
`${result.stderr}\n${result.error?.message ?? "no spawn error"}; signal=${result.signal}; elapsed=${result.elapsedMs}ms`;
const expectRejectedBeforeUpload = (result: ReturnType<typeof runCommand>) => {
const detail = failureDetail(result);
expect(result.error, detail).toBeUndefined();
expect(result.status, detail).toBeTypeOf("number");
expect(result.status, detail).not.toBe(0);
expect(existsSync(capturedBundle)).toBe(false);
};
const git = (cwd: string, args: string[]) => {
const result = runCommand("git", args, { cwd, env, encoding: "utf8", timeout: 10_000 });
expect(result.status, `${args.join(" ")}\n${failureDetail(result)}`).toBe(0);
return result.stdout.trim();
};
mkdirSync(path.join(origin, "scripts"), { recursive: true });
git(origin, ["init", "-q", "-b", "main"]);
writeFileSync(path.join(origin, ".gitignore"), ".tmp/\n*.ignored\n!reincluded.ignored\n");
writeFileSync(path.join(origin, ".gitattributes"), "* text=auto eol=lf\n");
writeFileSync(
path.join(origin, ".crabbox.yaml"),
"sync:\n exclude:\n - private-canary.txt\n - protected-deleted.ignored\n",
);
const restored = "old content beyond the receiver's shallow history\n";
mkdirSync(path.join(origin, "restored"));
writeFileSync(path.join(origin, "restored", "old.txt"), restored);
git(origin, ["add", "-A"]);
git(origin, ["commit", "-qm", "old content"]);
rmSync(path.join(origin, "restored"), { recursive: true });
git(origin, ["add", "-A"]);
git(origin, ["commit", "-qm", "remove old content"]);
git(origin, ["commit", "--allow-empty", "-qm", "advance history"]);
const unchanged = Buffer.concat(
Array.from({ length: 24576 }, (_, index) =>
createHash("sha256").update(`unchanged-${index}`).digest(),
),
);
writeFileSync(path.join(origin, "unchanged.bin"), unchanged);
const hiddenPath = "sparse-only/data.bin";
const hiddenBytes = Buffer.from([0, 255, 13, 10, 128]);
mkdirSync(path.join(origin, "sparse-only"));
writeFileSync(path.join(origin, hiddenPath), hiddenBytes);
for (const file of ["owner.txt", "deleted.txt", "rename-before.txt", "mode.sh"]) {
writeFileSync(path.join(origin, file), "base\n");
}
writeFileSync(path.join(origin, "tracked.ignored"), "tracked ignored base\n");
writeFileSync(path.join(origin, "reincluded.ignored"), "reincluded base\n");
if (replacedHistory) {
writeFileSync(path.join(origin, "removed-kind.ignored"), "old file kind\n");
git(origin, ["add", "-f", "removed-kind.ignored"]);
}
writeFileSync(path.join(origin, "raw-crlf.txt"), "base\n");
mkdirSync(path.join(origin, "source-dir"));
writeFileSync(path.join(origin, "source-dir", "kept.txt"), "source directory\n");
symlinkSync("source-dir", path.join(origin, "directory-alias"), "dir");
symlinkSync("owner.txt", path.join(origin, "alias"));
git(origin, ["add", "-f", "tracked.ignored"]);
// The leaf is deliberately inert: this test proves transport, not check lanes.
writeFileSync(
path.join(origin, "scripts/check-changed.mjs"),
'import fs from "node:fs"; if (process.env.TRANSPORT_FIXTURE_ARGV) fs.writeFileSync(process.env.TRANSPORT_FIXTURE_ARGV, JSON.stringify(process.argv.slice(2))); process.stdout.write("transport fixture reached\\n");\n',
);
copyFileSync(
path.join(origin, "scripts/check-changed.mjs"),
path.join(origin, "scripts/source-fixture.mjs"),
);
git(origin, ["add", "-A"]);
git(origin, ["commit", "-qm", "base"]);
const base = git(origin, ["rev-parse", "HEAD"]);
const partial = provider === "blacksmith-testbox" && shallow;
git(origin, ["config", "uploadpack.allowFilter", "true"]);
git(root, [
"clone",
"-q",
...(shallow ? ["--depth=1"] : []),
...(partial ? ["--filter=blob:none", "--no-checkout"] : []),
`file://${origin}`,
producer,
]);
if (partial) {
git(producer, ["sparse-checkout", "set", "--no-cone", "/*", "!/sparse-only/"]);
git(producer, ["read-tree", "-mu", "HEAD"]);
const hiddenObject = git(origin, ["rev-parse", `HEAD:${hiddenPath}`]);
expect(git(producer, ["rev-list", "--objects", "--all", "--missing=print"])).toContain(
`?${hiddenObject}`,
);
}
expect(git(producer, ["rev-parse", "--is-shallow-repository"])).toBe(String(shallow));
const alias = provider === "aws" && scenario !== "arbitrary" ? "origin/release/fixture" : "";
if (alias) {
git(producer, ["update-ref", `refs/remotes/${alias}`, base]);
}
if (!shallow) {
git(producer, ["repack", "-adb"]);
expect(
readdirSync(path.join(producer, ".git", "objects", "pack")).some((file) =>
file.endsWith(".bitmap"),
),
).toBe(true);
}
const fixtureWrapper = path.join(producer, ".tmp", "crabbox-wrapper.mjs");
mkdirSync(path.dirname(fixtureWrapper), { recursive: true });
copyFileSync(realBundledWrapperPath, fixtureWrapper);
const sourceCommand =
provider === "blacksmith-testbox" || scenario === "arbitrary"
? "scripts/source-fixture.mjs"
: "scripts/check-changed.mjs";
const special = "space ' quote ; $(touch injected) `touch injected` & |";
const sourceArgs = alias ? ["--base", alias, special] : [special];
const sourceArgv = ["node", sourceCommand, ...sourceArgs];
const shellCommand = sourceArgv.map(shellQuote).join(" ");
const scriptBody = '#!/usr/bin/env bash\nexec "$@"\n';
const scriptInput = path.join(root, "input.sh");
writeFileSync(scriptInput, scriptBody);
const runSender = (
mode: "direct" | "shell" | "script" | "stdin" = "direct",
delegatedArgs?: string[],
) => {
const payload =
mode === "script"
? ["--script", scriptInput, "--", ...sourceArgv]
: mode === "stdin"
? ["--script-stdin", "--", ...sourceArgv]
: mode === "shell"
? ["--shell", "--", `true; ${shellCommand}`]
: ["--", "node", sourceCommand, ...sourceArgs];
const result = runCommand(
process.execPath,
[fixtureWrapper, ...(delegatedArgs ?? ["run", "--provider", provider, ...payload])],
{
cwd: producer,
env,
encoding: "utf8",
input: mode === "stdin" ? scriptBody : undefined,
timeout: 10_000,
},
);
expect(result.status, failureDetail(result)).toBe(0);
const run = expectSuccessfulWrapperRun(result);
let changed: string[] = [];
if (provider === "blacksmith-testbox") {
const native = JSON.parse(
readFileSync(env.OPENCLAW_FAKE_CRABBOX_NATIVE_SYNC_CAPTURE, "utf8"),
);
const source = git(producer, ["rev-parse", "HEAD"]);
expect(native).toMatchObject({ base, source, head: source });
expect(native.changed).toContain(".openclaw-crabbox-changed-gate.bundle");
changed = native.changed;
}
if (provider === "blacksmith-testbox") {
expectIdleSourceMirror(run.output.cwd, producer, path.join(root, "sync"));
expect(readFileSync(path.join(run.output.cwd, "owner.txt"))).toEqual(
readFileSync(path.join(producer, "owner.txt")),
);
} else {
expect(existsSync(run.output.cwd)).toBe(false);
expect(readdirSync(path.join(root, "sync"))).toEqual([]);
}
return {
remoteCommand: run.output.scriptContent || run.remoteCommand,
sourceFlags: run.output.args
.slice(0, run.output.args.indexOf("--"))
.filter((arg) => /^--script(?:-stdin)?(?:=|$)/u.test(arg)),
remoteArgs: run.output.scriptContent
? run.output.args.slice(run.output.args.indexOf("--") + 1)
: [],
bundle: readFileSync(capturedBundle),
changed,
};
};
const receive = (
name: string,
remoteCommand: string,
bundle?: Buffer,
source = origin,
extraEnv: NodeJS.ProcessEnv = {},
nativeSeed = true,
remoteArgs: string[] = [],
prepareReceiver?: (receiver: string) => void,
) => {
const receiver = path.join(root, name);
if (source === origin && nativeSeed) {
git(root, ["clone", "-q", `file://${origin}`, receiver]);
if (lstatSync(path.join(producer, "source-dir")).isSymbolicLink()) {
rmSync(path.join(receiver, "source-dir"), { recursive: true });
symlinkSync(
readlinkSync(path.join(producer, "source-dir"), { encoding: "buffer" }),
path.join(receiver, "source-dir"),
"dir",
);
}
for (const file of [
"tracked.ignored",
"reincluded.ignored",
"unchanged.bin",
"alias",
"directory-alias",
]) {
rmSync(path.join(receiver, file), { force: true });
}
writeFileSync(path.join(receiver, "owner.txt"), "native stale bytes\n");
chmodSync(path.join(receiver, "mode.sh"), 0o644);
// The base index has never known these ignored branch/staged paths.
for (const file of [...deletedOwned, ...retainedPrivate]) {
mkdirSync(path.dirname(path.join(receiver, file)), { recursive: true });
if (file === deletedOwned[2]) {
symlinkSync(deletionReferent, path.join(receiver, file), "dir");
} else {
writeFileSync(path.join(receiver, file), "stale ignored bytes\n");
}
}
writeFileSync(
path.join(receiver, "deleted-dir.ignored", "runtime.txt"),
"private runtime\n",
);
if (replacedHistory) {
rmSync(path.join(receiver, "removed-kind.ignored"));
mkdirSync(path.join(receiver, "removed-kind.ignored"));
writeFileSync(
path.join(receiver, "removed-kind.ignored", "owned.txt"),
"stale child\n",
);
}
} else {
mkdirSync(receiver);
}
prepareReceiver?.(receiver);
const transport =
provider === "blacksmith-testbox" ? path.join(root, `${name}-transport`) : receiver;
if (transport !== receiver) {
mkdirSync(path.join(transport, ".git"), { recursive: true });
symlinkSync(
realpathSync(receiver),
path.join(transport, ".git", "crabbox-artifact-root"),
"dir",
);
}
if (bundle) {
writeFileSync(path.join(transport, ".openclaw-crabbox-changed-gate.bundle"), bundle);
}
const result = runCommand("bash", ["-c", remoteCommand, "receiver-script", ...remoteArgs], {
cwd: transport,
encoding: "utf8",
timeout: 10_000,
env: {
...env,
GIT_CONFIG_COUNT: "2",
GIT_CONFIG_KEY_0: `url.file://${source}.insteadOf`,
GIT_CONFIG_VALUE_0: "https://github.com/openclaw/openclaw.git",
GIT_CONFIG_KEY_1: "protocol.file.allow",
GIT_CONFIG_VALUE_1: "always",
...extraEnv,
},
});
expect(result.error, failureDetail(result)).toBeUndefined();
if (transport !== receiver && result.status === 0) {
expect(readlinkSync(path.join(transport, ".git", "crabbox-artifact-root"))).toBe(
realpathSync(receiver),
);
expect(existsSync(path.join(transport, "owner.txt"))).toBe(false);
}
return { receiver, result };
};
if (scenario !== "transport" && scenario !== "arbitrary") {
const installOwner = ".github/actions/setup-node-env/install-dependencies.sh";
const ownerPath = path.join(repoRoot, installOwner);
// Baseline uses the same action-owned recipe before its pure extraction.
const action: { runs: { steps: Array<{ name?: string; run?: string }> } } = parse(
readFileSync(path.join(repoRoot, ".github/actions/setup-node-env/action.yml"), "utf8"),
);
const installStep = action.runs.steps.find((step) => step.name === "Install dependencies");
if (!installStep?.run) {
throw new Error("Missing canonical install recipe");
}
const installer: string = existsSync(ownerPath)
? readFileSync(ownerPath, "utf8")
: "#!/usr/bin/env bash\n" + installStep.run;
const { packageManager }: { packageManager: string } = JSON.parse(
readFileSync(path.join(repoRoot, "package.json"), "utf8"),
);
const { environment } = pnpmLockfileDocuments(
readFileSync(path.join(repoRoot, "pnpm-lock.yaml"), "utf8"),
);
const dependencyRoot =
preparedDependencyRoot ?? dependencyTempDirs.make("openclaw-capsule-dependencies-");
const hydratedSource = path.join(dependencyRoot, "a");
const selectedSource = path.join(dependencyRoot, "b");
const dependencyEnv = {
...env,
CI: "true",
PATH: [path.dirname(process.execPath), env.PATH].join(path.delimiter),
COREPACK_HOME: path.join(dependencyRoot, "corepack"),
PNPM_CONFIG_STORE_DIR: path.join(dependencyRoot, "store"),
PNPM_CONFIG_CACHE_DIR: path.join(dependencyRoot, "cache"),
};
const runPnpm = (directory: string, args: string[]) => {
const runner = resolvePnpmRunner({ cwd: directory, env: dependencyEnv });
const result = runCommand(runner.command, [...runner.args, ...args], {
cwd: directory,
env: dependencyEnv,
encoding: "utf8",
});
expect(result.error, failureDetail(result)).toBeUndefined();
expect(result.status, failureDetail(result)).toBe(0);
return result;
};
const rootManifest = { name: "capsule-dependency-fixture", private: true, packageManager };
const consumerManifest = (graph: "a" | "b") => ({
name: "capsule-dependency-consumer",
private: true,
dependencies: { "capsule-proof-dep": "file:../../fixture-deps/" + graph },
});
const writeDependencySource = (directory: string, graph: "a" | "b") => {
for (const version of ["a", "b"]) {
const dependency = path.join(directory, "fixture-deps", version);
mkdirSync(dependency, { recursive: true });
writeFileSync(
path.join(dependency, "package.json"),
JSON.stringify({
name: "capsule-proof-dep",
version: version === "a" ? "1.0.0" : "2.0.0",
main: "index.cjs",
}),
);
writeFileSync(
path.join(dependency, "index.cjs"),
"module.exports = 'graph-" + version + "';\n",
);
}
writeFileSync(path.join(directory, "package.json"), JSON.stringify(rootManifest));
mkdirSync(path.join(directory, "packages/consumer"), { recursive: true });
writeFileSync(
path.join(directory, "packages/consumer/package.json"),
JSON.stringify(consumerManifest(graph)),
);
writeFileSync(path.join(directory, "pnpm-workspace.yaml"), "packages:\n - packages/*\n");
if (environment !== null) {
writeFileSync(
path.join(directory, "pnpm-lock.yaml"),
"---\n" + environment + "\n---\n",
);
}
mkdirSync(path.join(directory, ".capsule-proof"), { recursive: true });
writeFileSync(
path.join(directory, ".gitignore"),
readFileSync(path.join(origin, ".gitignore"), "utf8") +
"\nnode_modules/\n.capsule-proof/\n",
);
};
if (!preparedDependencyRoot) {
writeDependencySource(hydratedSource, "a");
writeDependencySource(selectedSource, "b");
const version = runPnpm(hydratedSource, ["--version"]);
expect("pnpm@" + version.stdout.trim()).toBe(packageManager.split("+")[0]);
runPnpm(hydratedSource, ["install", "--lockfile-only"]);
runPnpm(hydratedSource, ["install", "--frozen-lockfile"]);
runPnpm(selectedSource, ["install", "--lockfile-only"]);
preparedDependencyRoot = dependencyRoot;
}
// Copy relative package links verbatim; each receiver owns its modules while
// retaining the prepared store identity required by pnpm's install metadata.
const copyOptions = { recursive: true, verbatimSymlinks: true };
cpSync(selectedSource, producer, copyOptions);
mkdirSync(path.dirname(path.join(producer, installOwner)), { recursive: true });
writeFileSync(path.join(producer, installOwner), installer);
writeFileSync(
path.join(producer, sourceCommand),
[
'import fs from "node:fs";',
'import { createRequire } from "node:module";',
'const actual = createRequire(new URL("../packages/consumer/package.json", import.meta.url))("capsule-proof-dep");',
'if (actual !== "graph-b") throw new Error("capsule dependency mismatch: " + actual);',
'fs.writeFileSync(".capsule-proof/payload", actual);',
'process.stdout.write("capsule graph-b\\n");',
].join("\n") + "\n",
);
if (scenario === "frozen") {
writeFileSync(
path.join(producer, "packages/consumer/package.json"),
JSON.stringify(consumerManifest("a")),
);
} else if (scenario.startsWith("lifecycle")) {
const mutation =
scenario === "lifecycle-staged-extra"
? [
'const fs = require("node:fs");',
'const { execFileSync } = require("node:child_process");',
'fs.writeFileSync("lifecycle-extra.mjs", "export const lifecycleExtra = true;\\n");',
'execFileSync("git", ["add", "--", "lifecycle-extra.mjs"]);',
'fs.writeFileSync(".capsule-proof/lifecycle", "staged-extra");',
]
: scenario === "lifecycle-base-ref"
? [
'const fs = require("node:fs");',
'const { execFileSync } = require("node:child_process");',
'const git = (args) => execFileSync("git", args, { encoding: "utf8" });',
'const before = git(["rev-parse", "refs/remotes/origin/main"]).trim();',
'const head = git(["rev-parse", "HEAD"]).trim();',
'const indexBefore = git(["ls-files", "--stage", "-v", "-z"]);',
'execFileSync("git", ["update-ref", "refs/remotes/origin/main", "HEAD"]);',
'const after = git(["rev-parse", "refs/remotes/origin/main"]).trim();',
'const indexAfter = git(["ls-files", "--stage", "-v", "-z"]);',
'fs.writeFileSync(".capsule-proof/lifecycle", JSON.stringify({ before, head, after, indexBefore, indexAfter }));',
]
: [
'const fs = require("node:fs");',
'fs.writeFileSync(".capsule-proof/lifecycle", "ran");',
'fs.writeFileSync("owner.txt", "lifecycle source drift\\n");',
];
writeFileSync(
path.join(producer, "scripts/capsule-lifecycle.cjs"),
mutation.join("\n") + "\n",
);
writeFileSync(
path.join(producer, "package.json"),
JSON.stringify({
...rootManifest,
scripts: { postinstall: "node scripts/capsule-lifecycle.cjs" },
}),
);
}
const selectedOwner = readFileSync(path.join(producer, "owner.txt"), "utf8");
const frozen = runSender();
let preparedGraph = "";
const imported = receive(
"dependency-" + scenario,
frozen.remoteCommand,
frozen.bundle,
origin,
{ ...dependencyEnv, COREPACK_ENABLE_NETWORK: "0" },
true,
[],
(receiver) => {
cpSync(hydratedSource, receiver, copyOptions);
const probe = runCommand(
process.execPath,
[
"-e",
'const dependency = require("node:module").createRequire(process.cwd() + "/packages/consumer/package.json"); process.stdout.write(JSON.stringify({ graph: dependency("capsule-proof-dep"), file: dependency.resolve("capsule-proof-dep") }))',
],
{
cwd: receiver,
env: dependencyEnv,
encoding: "utf8",
},
);
expect(probe.error, failureDetail(probe)).toBeUndefined();
expect(probe.status, failureDetail(probe)).toBe(0);
const prepared: { graph: string; file: string } = JSON.parse(probe.stdout);
preparedGraph = prepared.graph;
expect(prepared.file.startsWith(receiver + path.sep)).toBe(true);
},
);
expect(preparedGraph).toBe("graph-a");
const payload = path.join(imported.receiver, ".capsule-proof/payload");
if (scenario === "graph") {
expect(imported.result.status, failureDetail(imported.result)).toBe(0);
expect(imported.result.stdout).toBe("capsule graph-b\n");
expect(readFileSync(payload, "utf8")).toBe("graph-b");
expect(imported.result.stderr).toContain("[crabbox] verified source=");
} else {
// Prove the metadata mutation occurred before the intended rejection assertion.
if (scenario === "lifecycle-staged-extra" || scenario === "lifecycle-base-ref") {
expect(readFileSync(path.join(imported.receiver, "owner.txt"), "utf8")).toBe(
selectedOwner,
);
const lifecycle = readFileSync(
path.join(imported.receiver, ".capsule-proof/lifecycle"),
"utf8",
);
if (scenario === "lifecycle-staged-extra") {
expect(lifecycle).toBe("staged-extra");
expect(
readFileSync(path.join(imported.receiver, "lifecycle-extra.mjs"), "utf8"),
).toBe("export const lifecycleExtra = true;\n");
expect(git(imported.receiver, ["diff", "--cached", "--name-only"])).toBe(
"lifecycle-extra.mjs",
);
} else {
const mutation: {
before: string;
head: string;
after: string;
indexBefore: string;
indexAfter: string;
} = JSON.parse(lifecycle);
expect(mutation.before).not.toBe(mutation.head);
expect(mutation.after).toBe(mutation.head);
expect(git(imported.receiver, ["rev-parse", "refs/remotes/origin/main"])).toBe(
mutation.head,
);
expect(mutation.indexAfter).toBe(mutation.indexBefore);
expect(git(imported.receiver, ["ls-files", "--others", "--exclude-standard"])).toBe(
"",
);
}
}
expect(imported.result.status, failureDetail(imported.result)).toBe(2);
expect(existsSync(payload)).toBe(false);
expect(imported.result.stderr).not.toContain("[crabbox] verified source=");
if (scenario === "frozen") {
expect(imported.result.stdout + imported.result.stderr).toContain(
'Cannot install with "frozen-lockfile"',
);
expect(imported.result.stdout + imported.result.stderr).toContain(
'importers["packages/consumer"]',
);
expect(imported.result.stderr).toContain(
"selected-source frozen install failed; payload was not run",
);
} else if (scenario === "lifecycle") {
expect(
readFileSync(path.join(imported.receiver, ".capsule-proof/lifecycle"), "utf8"),
).toBe("ran");
expect(readFileSync(path.join(imported.receiver, "owner.txt"), "utf8")).toBe(
"lifecycle source drift\n",
);
expect(imported.result.stderr).toContain("source bytes mismatch: owner.txt");
} else {
expect(imported.result.stderr).toContain(
scenario === "lifecycle-staged-extra"
? "source index mismatch"
: "source comparison ref mismatch: refs/remotes/origin/main",
);
}
}
return;
}
if (scenario === "arbitrary") {
// A raw AWS sync can have no Git seed. The actual capsule receiver must
// establish source identity before an ordinary command can observe it.
for (const [name, args] of [
["node", ["node", sourceCommand, special]],
["bash", ["bash", "-c", 'exec node "$1" "$2"', "fixture", sourceCommand, special]],
] as const) {
const sent = runSender("direct", ["run", "--provider", provider, "--", ...args]);
const argvPath = path.join(root, `${name}-argv.json`);
const accepted = receive(
name,
sent.remoteCommand,
sent.bundle,
origin,
{ TRANSPORT_FIXTURE_ARGV: argvPath },
false,
);
expect(accepted.result.status, failureDetail(accepted.result)).toBe(0);
expect(accepted.result.stdout).toBe("transport fixture reached\n");
expect(JSON.parse(readFileSync(argvPath, "utf8"))).toEqual([special]);
expect(existsSync(path.join(accepted.receiver, "injected"))).toBe(false);
expect(git(accepted.receiver, ["rev-parse", "HEAD^{tree}"])).toBe(
git(producer, ["rev-parse", "HEAD^{tree}"]),
);
expect(git(accepted.receiver, ["status", "--porcelain=v1"])).toBe("");
const rejected = receive(
`${name}-missing`,
sent.remoteCommand,
undefined,
origin,
{},
false,
);
expect(rejected.result.status, failureDetail(rejected.result)).toBe(2);
expect(rejected.result.stdout).toBe("");
expect(existsSync(path.join(rejected.receiver, ".git"))).toBe(false);
}
return;
}
const empty = runSender();
expect(empty.bundle.length).toBeGreaterThan(0);
const unchangedRun = receive("unchanged", empty.remoteCommand, empty.bundle);
expect(unchangedRun.result.status, failureDetail(unchangedRun.result)).toBe(0);
expect(git(unchangedRun.receiver, ["rev-parse", "HEAD^{tree}"])).toBe(
git(origin, ["rev-parse", "HEAD^{tree}"]),
);
if (provider === "aws" && !shallow) {
const policy = "sync:\n exclude:\n - private-canary.txt\n";
const invocationLog = path.join(root, "policy-invocations.jsonl");
const runWithPolicy = (config: string) => {
rmSync(capturedBundle, { force: true });
writeFileSync(invocationLog, "");
return runCommand(
process.execPath,
[fixtureWrapper, "run", "--provider", provider, "--", "node", sourceCommand],
{
cwd: producer,
env: {
...env,
CRABBOX_CONFIG: config,
OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG: invocationLog,
},
encoding: "utf8",
timeout: 10_000,
},
);
};
const expectPolicyRefusal = (result: ReturnType<typeof runCommand>) => {
expectRejectedBeforeUpload(result);
expect(
readInvocations(invocationLog).filter(
(args) => args[0] === "run" && args[1] !== "--help",
),
).toEqual([]);
expect(readdirSync(path.join(root, "sync"))).toEqual([]);
};
// Exercise the real producer before the fake policy owner can omit a
// file. The fake sync-plan does not interpret CRABBOX_CONFIG exclusions.
for (const [kind, location] of [
["leaf", "external"],
["leaf", "internal"],
["ancestor", "external"],
["ancestor", "internal"],
] as const) {
const target = path.join(
location === "internal" ? producer : root,
`${kind}-${location}-target.ignored`,
);
const policyAlias = path.join(producer, `${kind}-${location}-policy.ignored`);
const config = path.join(policyAlias, "config.yaml");
mkdirSync(target);
writeFileSync(path.join(target, "config.yaml"), policy);
if (kind === "leaf") {
mkdirSync(policyAlias);
symlinkSync(path.join(target, "config.yaml"), config);
} else {
symlinkSync(target, policyAlias, "dir");
}
try {
const rejected = runWithPolicy(config);
expectPolicyRefusal(rejected);
expect(rejected.stderr).toContain("repository policy");
expect(rejected.stderr).toContain(path.relative(producer, config));
expect(readInvocations(invocationLog).some((args) => args[0] === "sync-plan")).toBe(
false,
);
expect(lstatSync(kind === "leaf" ? config : policyAlias).isSymbolicLink()).toBe(true);
expect(readFileSync(path.join(target, "config.yaml"), "utf8")).toBe(policy);
} finally {
rmSync(policyAlias, { recursive: true, force: true });
rmSync(target, { recursive: true, force: true });
}
}
const directoryPolicy = path.join(producer, "directory-policy.ignored");
mkdirSync(directoryPolicy);
try {
const rejected = runWithPolicy(directoryPolicy);
expectPolicyRefusal(rejected);
expect(rejected.stderr).toContain("non-regular repository policy");
expect(readInvocations(invocationLog).some((args) => args[0] === "sync-plan")).toBe(
false,
);
expect(lstatSync(directoryPolicy).isDirectory()).toBe(true);
} finally {
rmSync(directoryPolicy, { recursive: true });
}
const missing = runWithPolicy(path.join(producer, "missing-policy.ignored/config.yaml"));
expectSuccessfulWrapperRun(missing);
expect(existsSync(capturedBundle)).toBe(true);
expect(readInvocations(invocationLog).some((args) => args[0] === "sync-plan")).toBe(true);
expect(readdirSync(path.join(root, "sync"))).toEqual([]);
const regularPath = path.join(producer, "fixture-policy.yaml");
writeFileSync(regularPath, policy);
try {
const regular = expectSuccessfulWrapperRun(runWithPolicy(regularPath));
const retained = receive(
"regular-policy",
regular.remoteCommand,
readFileSync(capturedBundle),
);
expect(retained.result.status, failureDetail(retained.result)).toBe(0);
expect(readFileSync(path.join(retained.receiver, "fixture-policy.yaml"), "utf8")).toBe(
policy,
);
expect(readdirSync(path.join(root, "sync"))).toEqual([]);
} finally {
rmSync(regularPath);
}
const ignoredPath = path.join(producer, "runtime-policy.ignored");
writeFileSync(ignoredPath, policy);
try {
const excluded = runWithPolicy(ignoredPath);
expectPolicyRefusal(excluded);
expect(excluded.stderr).toContain("excluded repository runtime configuration");
expect(readInvocations(invocationLog).some((args) => args[0] === "sync-plan")).toBe(true);
expect(readFileSync(ignoredPath, "utf8")).toBe(policy);
} finally {
rmSync(ignoredPath);
}
}
writeFileSync(path.join(producer, "owner.txt"), "committed\n");
git(producer, ["add", "owner.txt"]);
writeFileSync(path.join(producer, "committed-only.txt"), "committed source\n");
git(producer, ["add", "committed-only.txt"]);
if (replacedHistory) {
rmSync(path.join(producer, "removed-kind.ignored"));
mkdirSync(path.join(producer, "removed-kind.ignored"));
writeFileSync(
path.join(producer, "removed-kind.ignored", "owned.txt"),
"new directory kind\n",
);
git(producer, ["add", "-A", "-f", "removed-kind.ignored"]);
}
for (const file of [...deletedOwned.slice(0, 2), deletedOwned[3]!, retainedPrivate[0]!]) {
mkdirSync(path.dirname(path.join(producer, file)), { recursive: true });
writeFileSync(path.join(producer, file), "branch-owned source\n");
git(producer, ["add", "-f", file]);
}
git(producer, ["commit", "-qm", "committed change"]);
if (replacedHistory) {
rmSync(path.join(producer, "removed-kind.ignored"), { recursive: true });
}
writeFileSync(path.join(producer, deletedOwned[2]!), "new staged ignored source\n");
git(producer, ["add", "-f", deletedOwned[2]!]);
for (const file of [...deletedOwned, retainedPrivate[0]!]) {
rmSync(path.join(producer, file));
}
git(producer, ["add", "-u", "staged-deleted.ignored"]);
writeFileSync(path.join(producer, "owner.txt"), "unstaged\n");
writeFileSync(path.join(producer, "untracked.txt"), "untracked\n");
mkdirSync(path.join(producer, "restored"));
writeFileSync(path.join(producer, "restored", "old.txt"), restored);
rmSync(path.join(producer, "deleted.txt"));
renameSync(path.join(producer, "rename-before.txt"), path.join(producer, "renamed.txt"));
chmodSync(path.join(producer, "mode.sh"), 0o755);
writeFileSync(path.join(producer, "tracked.ignored"), "changed tracked ignored\r\n");
writeFileSync(path.join(producer, "reincluded.ignored"), "changed reincluded\r\n");
writeFileSync(path.join(producer, "raw-crlf.txt"), "raw bytes\r\nsecond line\r\n");
writeFileSync(
path.join(producer, "dirty.bin"),
Buffer.concat(
Array.from({ length: 12288 }, (_, index) =>
createHash("sha256").update(`dirty-${index}`).digest(),
),
),
);
writeFileSync(path.join(producer, "staged.ignored"), "new ignored staged\r\n");
git(producer, ["add", "-f", "staged.ignored"]);
const external = path.join(root, "external");
mkdirSync(external);
writeFileSync(path.join(external, "canary.txt"), "external referent must stay local\n");
writeFileSync(path.join(external, "kept.txt"), "shadowed tracked referent must stay local\n");
rmSync(path.join(producer, "source-dir"), { recursive: true });
symlinkSync(external, path.join(producer, "source-dir"), "dir");
rmSync(path.join(producer, "directory-alias"));
symlinkSync(external, path.join(producer, "directory-alias"), "dir");
rmSync(path.join(producer, "alias"));
symlinkSync(Buffer.from([46, 47, 255, 10]), path.join(producer, "alias"));
const privatePaths = [
"private-canary.txt",
"global-canary.txt",
"info-canary.txt",
"secret.ignored",
];
writeFileSync(path.join(producer, ".git", "info", "exclude"), "info-canary.txt\n");
mkdirSync(home, { recursive: true });
const globalExclude = path.join(home, "git-exclude");
const localExcludes =
provider === "aws" && !shallow
? "relative"
: provider === "blacksmith-testbox" && shallow
? "empty"
: "";
writeFileSync(globalExclude, "global-canary.txt\n" + (localExcludes ? "keep.txt\n" : ""));
writeFileSync(path.join(home, ".gitconfig"), `[core]\n excludesFile = ${globalExclude}\n`);
env.GIT_CONFIG_GLOBAL = path.join(home, ".gitconfig");
if (localExcludes) {
writeFileSync(path.join(producer, "keep.txt"), "eligible under the local override\n");
const policy = path.join(root, "local-exclude-policy");
writeFileSync(policy, "global-canary.txt\n");
symlinkSync(policy, path.join(producer, "exclusion-policy"));
writeFileSync(
path.join(producer, ".git", "info", "exclude"),
"info-canary.txt\nexclusion-policy\nglobal-canary.txt\n",
);
git(producer, [
"config",
"--local",
"core.excludesFile",
localExcludes === "empty" ? "" : "exclusion-policy",
]);
}
for (const file of privatePaths) {
writeFileSync(path.join(producer, file), "private fixture canary\n");
}
git(producer, ["add", "mode.sh", "rename-before.txt", "renamed.txt"]);
const sparse = provider === "blacksmith-testbox";
if (sparse) {
git(producer, ["sparse-checkout", "set", "--no-cone", "/*", "!/sparse-only/"]);
expect(existsSync(path.join(producer, hiddenPath))).toBe(false);
}
const headBefore = git(producer, ["rev-parse", "HEAD"]);
const indexBefore = readFileSync(path.join(producer, ".git", "index"));
const statusBefore = git(producer, ["status", "--porcelain=v1"]);
const shallowBefore = shallow
? readFileSync(path.join(producer, ".git", "shallow"))
: undefined;
const candidate = runSender();
if (provider === "blacksmith-testbox") {
expect(candidate.changed).toEqual(
expect.arrayContaining([
"committed-only.txt",
"owner.txt",
"deleted.txt",
"rename-before.txt",
"renamed.txt",
"staged.ignored",
"untracked.txt",
]),
);
expect(candidate.changed).not.toContain("unchanged.bin");
for (const file of privatePaths) {
expect(candidate.changed).not.toContain(file);
}
}
// A change must not resend the unchanged, incompressible base blob.
expect(candidate.bundle.length).toBeLessThan(unchanged.length);
// Exercise a full 256 KiB hash chunk followed by a partial final chunk.
expect(candidate.bundle.length).toBeGreaterThan(256 * 1024);
expect(candidate.bundle.length).toBeLessThan(512 * 1024);
expect(git(producer, ["rev-parse", "HEAD"])).toBe(headBefore);
expect(readFileSync(path.join(producer, ".git", "index"))).toEqual(indexBefore);
expect(git(producer, ["status", "--porcelain=v1"])).toBe(statusBefore);
expect(
shallow
? readFileSync(path.join(producer, ".git", "shallow"))
: existsSync(path.join(producer, ".git", "shallow")),
).toEqual(shallowBefore ?? false);
const newerFiles = [
"newer-source.txt",
"newer-source-link",
"newer-directory/owned.txt",
"newer-directory/.gitignore",
"newer-directory/hidden/.gitignore",
"newer-directory/hidden/generated.js",
];
const newerCaches = [
"newer-directory/cache.ignored",
"newer-directory/changed-cache.ignored",
"newer-directory/.cache.ignored/.gitignore",
"newer-directory/.changed-cache.ignored/.gitignore",
"newer-directory/.untracked-cache.ignored/.gitignore",
];
const prepareNewerReceiver = (receiver: string) => {
writeFileSync(path.join(receiver, newerFiles[0]!), "newer workflow source\n");
symlinkSync("newer-source.txt", path.join(receiver, newerFiles[1]!));
mkdirSync(path.join(receiver, "newer-directory"));
writeFileSync(path.join(receiver, newerFiles[2]!), "newer nested source\n");
mkdirSync(path.join(receiver, "newer-directory/hidden"));
writeFileSync(path.join(receiver, newerFiles[3]!), "hidden/\n!*.ignored\n");
writeFileSync(path.join(receiver, newerFiles[4]!), "generated.js\n");
writeFileSync(path.join(receiver, newerFiles[5]!), "newer hidden source\n");
writeFileSync(path.join(receiver, "newer-private.ignored"), "retained private bytes\n");
for (const file of newerCaches) {
mkdirSync(path.dirname(path.join(receiver, file)), { recursive: true });
writeFileSync(path.join(receiver, file), "retained cache bytes\n");
}
git(receiver, [
"add",
"-f",
...newerFiles,
...newerCaches.slice(0, -1),
"newer-private.ignored",
]);
git(receiver, ["commit", "-qm", "newer workflow source"]);
// A stale execution index cannot erase ownership recorded by its commit.
git(receiver, ["update-index", "--force-remove", ...newerFiles]);
writeFileSync(path.join(receiver, "newer-private.ignored"), "changed private bytes\n");
writeFileSync(path.join(receiver, newerCaches[1]!), "changed cache bytes\n");
writeFileSync(path.join(receiver, newerCaches[3]!), "changed cache bytes\n");
};
const imported = receive(
"candidate",
candidate.remoteCommand,
candidate.bundle,
origin,
{
GIT_DIR: path.join(root, "unrelated-git"),
GIT_WORK_TREE: root,
GIT_INDEX_FILE: path.join(root, "unrelated-index"),
GIT_COMMON_DIR: path.join(root, "unrelated-common"),
GIT_OBJECT_DIRECTORY: path.join(root, "unrelated-objects"),
GIT_ALTERNATE_OBJECT_DIRECTORIES: path.join(root, "unrelated-alternates"),
},
true,
[],
provider === "blacksmith-testbox" ? prepareNewerReceiver : undefined,
);
expect(imported.result.status, failureDetail(imported.result)).toBe(0);
expect(imported.result.stdout).toBe("transport fixture reached\n");
expect(existsSync(path.join(imported.receiver, "injected"))).toBe(false);
if (provider === "blacksmith-testbox") {
for (const file of newerFiles) {
expect(() => lstatSync(path.join(imported.receiver, file))).toThrow();
}
for (const [index, file] of newerCaches.entries()) {
expect(readFileSync(path.join(imported.receiver, file), "utf8")).toBe(
index === 1 || index === 3 ? "changed cache bytes\n" : "retained cache bytes\n",
);
}
expect(readFileSync(path.join(imported.receiver, "newer-private.ignored"), "utf8")).toBe(
"changed private bytes\n",
);
}
expect(git(imported.receiver, ["rev-parse", "HEAD^"])).toBe(base);
expect(git(imported.receiver, ["rev-list", "--count", "HEAD"])).toBe("3");
if (alias) {
expect(git(imported.receiver, ["rev-parse", alias])).toBe(base);
}
expect(readFileSync(path.join(imported.receiver, hiddenPath))).toEqual(hiddenBytes);
expect(imported.result.stderr).toContain(headBefore);
expect(git(imported.receiver, ["rev-parse", "HEAD"])).not.toBe(headBefore);
expect(readFileSync(path.join(imported.receiver, "unchanged.bin"))).toEqual(unchanged);
expect(readFileSync(path.join(imported.receiver, "owner.txt"), "utf8")).toBe("unstaged\n");
expect(readFileSync(path.join(imported.receiver, "untracked.txt"), "utf8")).toBe(
"untracked\n",
);
expect(existsSync(path.join(imported.receiver, "deleted.txt"))).toBe(false);
expect(existsSync(path.join(imported.receiver, "rename-before.txt"))).toBe(false);
for (const file of deletedOwned) {
expect(existsSync(path.join(imported.receiver, file)), file).toBe(false);
}
if (replacedHistory) {
expect(existsSync(path.join(imported.receiver, "removed-kind.ignored"))).toBe(false);
}
for (const file of retainedPrivate) {
expect(readFileSync(path.join(imported.receiver, file), "utf8")).toBe(
"stale ignored bytes\n",
);
}
expect(
readFileSync(path.join(imported.receiver, "deleted-dir.ignored", "runtime.txt"), "utf8"),
).toBe("private runtime\n");
expect(readFileSync(path.join(deletionReferent, "canary.txt"), "utf8")).toBe(
"private referent\n",
);
if (localExcludes) {
expect(readFileSync(path.join(imported.receiver, "keep.txt"), "utf8")).toBe(
"eligible under the local override\n",
);
expect(existsSync(path.join(imported.receiver, "exclusion-policy"))).toBe(false);
}
expect(readFileSync(path.join(imported.receiver, "renamed.txt"), "utf8")).toBe("base\n");
expect(readFileSync(path.join(imported.receiver, "restored", "old.txt"), "utf8")).toBe(
restored,
);
expect(git(imported.receiver, ["ls-tree", "HEAD", "alias"])).toMatch(/^120000 blob /u);
expect(git(imported.receiver, ["ls-tree", "HEAD", "mode.sh"])).toMatch(/^100755 blob /u);
const selected = git(producer, [
"ls-files",
"--cached",
"--others",
"--exclude-standard",
"-z",
])
.split("\0")
.filter((file) => file && !privatePaths.includes(file) && file !== "source-dir/kept.txt")
.filter((file) => {
try {
return !lstatSync(path.join(producer, file)).isDirectory();
} catch {
return false;
}
});
const sourceManifest = (directory: string, files: string[]) =>
[...new Set(files)].toSorted().map((file): [string, string, string] => {
const fullPath = path.join(directory, file);
const stat = lstatSync(fullPath);
const symlink = stat.isSymbolicLink();
return [
file,
symlink ? "120000" : stat.mode & 0o100 ? "100755" : "100644",
createHash("sha256")
.update(
symlink ? readlinkSync(fullPath, { encoding: "buffer" }) : readFileSync(fullPath),
)
.digest("hex"),
];
});
const remotePaths = git(imported.receiver, ["ls-files", "-z"]).split("\0").filter(Boolean);
const expectedManifest = sourceManifest(producer, selected);
if (sparse) {
expectedManifest.push(...sourceManifest(origin, [hiddenPath]));
}
expectedManifest.sort((left, right) => left[0].localeCompare(right[0]));
expect(
sourceManifest(imported.receiver, remotePaths).toSorted((left, right) =>
left[0].localeCompare(right[0]),
),
).toEqual(expectedManifest);
expect(git(imported.receiver, ["ls-files", "--others", "--exclude-standard"])).toBe("");
for (const file of privatePaths) {
expect(existsSync(path.join(imported.receiver, file)), file).toBe(false);
}
expect(
readlinkSync(path.join(imported.receiver, "directory-alias"), { encoding: "buffer" }),
).toEqual(Buffer.from(external));
expect(readFileSync(path.join(external, "canary.txt"), "utf8")).toBe(
"external referent must stay local\n",
);
expect(readFileSync(path.join(external, "kept.txt"), "utf8")).toBe(
"shadowed tracked referent must stay local\n",
);
expect(git(imported.receiver, ["ls-tree", "HEAD", "source-dir"])).toMatch(/^120000 blob /u);
expect(
git(imported.receiver, ["diff", "--no-renames", "--name-only", base, "HEAD"]).split("\n"),
).toEqual(
expect.arrayContaining([
"raw-crlf.txt",
"tracked.ignored",
"reincluded.ignored",
"staged.ignored",
"deleted.txt",
"mode.sh",
"alias",
"directory-alias",
]),
);
if (provider === "aws" && !shallow) {
// AWS supports uploaded SSH scripts; Blacksmith supports commands only.
for (const { name, mode, flags } of [
{ name: "file", mode: "script", flags: ["--script", scriptInput] },
{ name: "stdin", mode: "stdin", flags: ["--script-stdin"] },
{ name: "stdin-empty-file-after", mode: "stdin", flags: ["--script-stdin", "--script="] },
{
name: "empty-file-before-stdin",
mode: "stdin",
flags: ["--script=", "--script-stdin"],
},
{
name: "repeated-stdin",
mode: "stdin",
flags: ["--script-stdin", "--script-stdin=true"],
},
{
name: "file-disabled-stdin",
mode: "script",
flags: ["--script-stdin", "--script-stdin=false", "--script", scriptInput],
},
] as const) {
const script = runSender(mode, [
"run",
"--provider",
provider,
...flags,
"--",
...sourceArgv,
]);
expect(script.sourceFlags).toEqual(["--script"]);
const argvPath = path.join(root, `${name}-argv.json`);
const accepted = receive(
`${name}-candidate`,
script.remoteCommand,
script.bundle,
origin,
{ TRANSPORT_FIXTURE_ARGV: argvPath },
true,
script.remoteArgs,
);
expect(accepted.result.status, failureDetail(accepted.result)).toBe(0);
expect(accepted.result.stdout).toBe("transport fixture reached\n");
expect(JSON.parse(readFileSync(argvPath, "utf8"))).toEqual(sourceArgs);
expect(existsSync(path.join(accepted.receiver, "injected"))).toBe(false);
expect(
sourceManifest(accepted.receiver, remotePaths).toSorted((left, right) =>
left[0].localeCompare(right[0]),
),
).toEqual(expectedManifest);
rmSync(argvPath);
const rejected = receive(
`${name}-missing-capsule`,
script.remoteCommand,
undefined,
origin,
{ TRANSPORT_FIXTURE_ARGV: argvPath },
false,
script.remoteArgs,
);
expect(rejected.result.status, failureDetail(rejected.result)).toBe(2);
expect(rejected.result.stdout).toBe("");
expect(existsSync(argvPath)).toBe(false);
expect(existsSync(path.join(rejected.receiver, ".git"))).toBe(false);
}
}
// Shared receiver failure paths need one full real-Git fixture; provider/history
// variants above retain independent successful source identity checks.
if (provider === "blacksmith-testbox" && !shallow) {
const errnoFor = (code: string) =>
[...getSystemErrorMap()].find(([, [name]]) => name === code)?.[0];
const gitText = {
ref: "fatal: couldn't find remote ref fixture\n",
object: "fatal: pack has bad object at offset 12\n",
dns: "fatal: unable to access 'https://private.invalid/repo': Could not resolve host: private.invalid\n",
connection:
"fatal: unable to access 'https://private.invalid/repo': Failed to connect to private.invalid port 443\n",
auth: "fatal: Authentication failed for 'https://private.invalid/repo'\n",
nearAuth: "warning: authentication failed later PRIVATE_SENTINEL\n",
nearRef: "fatal: couldn't find remote reference PRIVATE_SENTINEL\n",
};
const fetchFailures = [
["base-fetch", "no-space", "ENOSPC"],
["capsule-fetch", "permission-denied", "EACCES"],
["base-fetch", "permission-denied", "EPERM"],
["capsule-fetch", "command-unavailable", "ENOENT"],
["base-fetch", "output-limit", "ENOBUFS"],
["capsule-fetch", "terminated", undefined, "SIGTERM"],
["base-fetch", "remote-ref-missing", undefined, undefined, gitText.ref],
["capsule-fetch", "invalid-object-data", undefined, undefined, gitText.object],
["base-fetch", "dns", undefined, undefined, gitText.dns],
["capsule-fetch", "connection", undefined, undefined, gitText.connection],
["base-fetch", "auth", undefined, undefined, gitText.auth],
["capsule-fetch", "unknown", undefined, undefined, gitText.nearAuth],
["base-fetch", "unknown", undefined, undefined, gitText.nearRef],
] as const;
for (const [index, [phase, cause, code, signal, stderr]] of fetchFailures.entries()) {
const preload = path.join(root, `fetch-failure-${index}.cjs`);
const errno = code ? errnoFor(code) : undefined;
writeFileSync(
preload,
`const cp = require("node:child_process");
const original = cp.spawnSync;
const fault = ${JSON.stringify({ phase, code, errno, signal, stderr })};
cp.spawnSync = (command, args, options) => {
const fetchIndex = args.indexOf("fetch");
if (command !== "git" || fetchIndex < 0 || args.slice(fetchIndex + 1).includes("origin") !== (fault.phase === "base-fetch"))
return original(command, args, options);
return { status: fault.code || fault.signal ? null : 128, signal: fault.signal ?? null,
stdout: Buffer.alloc(0), stderr: Buffer.from(fault.stderr ?? "PRIVATE_SENTINEL\\n"),
error: fault.code ? Object.assign(new Error("PRIVATE_ERROR_MESSAGE"), { code: fault.code, errno: fault.errno }) : undefined };
};\n`,
);
const argvPath = path.join(root, `fetch-failure-${index}.json`);
let priorIndex: Buffer | undefined;
const rejected = receive(
`fetch-failure-${index}`,
candidate.remoteCommand,
candidate.bundle,
origin,
{ NODE_OPTIONS: `--require=${preload}`, TRANSPORT_FIXTURE_ARGV: argvPath },
true,
[],
(receiver) => {
priorIndex = readFileSync(path.join(receiver, ".git", "index"));
},
);
const prefix = "[crabbox] source verification failed: source Git operation failed: ";
const line = rejected.result.stderr.split("\n").find((entry) => entry.startsWith(prefix));
expect(line, failureDetail(rejected.result)).toBeDefined();
expect(JSON.parse(line!.slice(prefix.length))).toEqual({
phase,
baseSha: base,
status: code || signal ? null : 128,
signal: signal ?? null,
spawnError: Boolean(code),
code: code ?? null,
errno: errno ?? null,
cause,
});
expect(rejected.result.status, failureDetail(rejected.result)).toBe(2);
expect(rejected.result.stdout).toBe("");
expect(rejected.result.stderr).not.toMatch(/PRIVATE_|private\.invalid/u);
expect(existsSync(argvPath)).toBe(false);
expect(git(rejected.receiver, ["rev-parse", "HEAD"])).toBe(base);
expect(readFileSync(path.join(rejected.receiver, ".git", "index"))).toEqual(priorIndex);
expect(readFileSync(path.join(rejected.receiver, "owner.txt"), "utf8")).toBe(
"native stale bytes\n",
);
expect(
readdirSync(rejected.receiver).filter((file) => file.startsWith(".openclaw-source-")),
).toEqual([]);
}
for (const [fault, file, message] of [
["bytes", "newer-source.txt", "source bytes mismatch"],
["mode", "newer-source.txt", "source mode mismatch"],
["kind", "newer-source.txt", "source mode mismatch"],
["link", "newer-source-link", "source bytes mismatch"],
["parent", "newer-directory", "unexpected source entry"],
["untracked", "unowned.txt", "unexpected source entry"],
["index", "unowned.txt", "unexpected source entry"],
["hidden-untracked", "newer-directory/hidden/unknown.js", "unexpected source entry"],
["hidden-bytes", "newer-directory/hidden/generated.js", "source bytes mismatch"],
] as const) {
let retained: ReturnType<typeof sourceManifest> = [];
let priorHead = "";
const rejected = receive(
`newer-${fault}`,
candidate.remoteCommand,
candidate.bundle,
origin,
{},
true,
[],
(receiver) => {
prepareNewerReceiver(receiver);
priorHead = git(receiver, ["rev-parse", "HEAD"]);
const fullPath = path.join(receiver, file);
if (fault === "mode") {
chmodSync(fullPath, 0o755);
} else if (fault === "kind" || fault === "link" || fault === "parent") {
rmSync(fullPath, { recursive: true });
symlinkSync(
fault === "parent" ? deletionReferent : path.join(deletionReferent, "canary.txt"),
fullPath,
);
} else {
writeFileSync(fullPath, "retained changed bytes\n");
}
if (fault === "index") {
git(receiver, ["add", file]);
}
retained = sourceManifest(receiver, [file]);
},
);
expect(rejected.result.status, failureDetail(rejected.result)).toBe(2);
expect(rejected.result.stderr, fault).toContain(message);
expect(rejected.result.stdout, fault).not.toContain("transport fixture reached");
expect(
sourceManifest(
rejected.receiver,
retained.map(([retainedFile]) => retainedFile),
),
fault,
).toEqual(retained);
expect(git(rejected.receiver, ["rev-parse", "HEAD"]), fault).toBe(priorHead);
expect(readFileSync(path.join(deletionReferent, "canary.txt"), "utf8")).toBe(
"private referent\n",
);
}
const corrupt = Buffer.from(candidate.bundle);
const lastIndex = corrupt.length - 1;
corrupt.writeUInt8(corrupt.readUInt8(lastIndex) ^ 1, lastIndex);
const wrongBase = git(imported.receiver, [
"commit-tree",
`${base}^{tree}`,
"-m",
"other base",
]);
const wrongCarrier = git(imported.receiver, [
"commit-tree",
"HEAD^{tree}",
"-p",
wrongBase,
"-m",
"other prerequisite",
]);
git(imported.receiver, ["update-ref", "HEAD", wrongCarrier]);
const emptyTree = git(imported.receiver, ["hash-object", "-w", "-t", "tree", "--stdin"]);
const emptyCarrier = git(imported.receiver, [
"commit-tree",
emptyTree,
"-p",
base,
"-m",
"empty source fixture",
]);
git(imported.receiver, ["update-ref", "refs/openclaw/source-capsule", emptyCarrier]);
const emptyTreeBundle = path.join(root, "empty-tree.bundle");
git(imported.receiver, [
"bundle",
"create",
emptyTreeBundle,
"refs/openclaw/source-capsule",
`^${base}`,
]);
const wrongBundle = path.join(root, "wrong-base.bundle");
git(imported.receiver, ["bundle", "create", wrongBundle, "HEAD", `^${wrongBase}`]);
for (const [name, bundle] of [
["missing-bundle", undefined],
["empty-bundle", Buffer.alloc(0)],
["valid-empty-tree-bundle", readFileSync(emptyTreeBundle)],
["stale-valid-bundle", empty.bundle],
["truncated-bundle", candidate.bundle.subarray(0, candidate.bundle.length / 2)],
["wrong-base-bundle", readFileSync(wrongBundle)],
["corrupt-bundle", corrupt],
] as const) {
const rejected = receive(name, candidate.remoteCommand, bundle, origin, {}, false);
expect(rejected.result.status, failureDetail(rejected.result)).toBe(2);
expect(rejected.result.stdout).not.toContain("transport fixture reached");
expect(existsSync(path.join(rejected.receiver, ".git"))).toBe(false);
}
const shell = runSender("shell");
const acceptedShell = receive("shell-candidate", shell.remoteCommand, shell.bundle);
expect(acceptedShell.result.status, failureDetail(acceptedShell.result)).toBe(0);
expect(acceptedShell.result.stdout).toBe("transport fixture reached\n");
expect(existsSync(path.join(acceptedShell.receiver, "injected"))).toBe(false);
const rejectedShell = receive(
"shell-missing-capsule",
shell.remoteCommand,
undefined,
origin,
{},
false,
);
expect(rejectedShell.result.status, failureDetail(rejectedShell.result)).toBe(2);
expect(rejectedShell.result.stdout).toBe("");
expect(existsSync(path.join(rejectedShell.receiver, ".git"))).toBe(false);
for (const extraEnv of [
{ OPENCLAW_FAKE_CRABBOX_SELECTION_COUNT_DELTA: "1" },
{ OPENCLAW_FAKE_CRABBOX_SELECTION_UNKNOWN_PATH: "1" },
{
OPENCLAW_FAKE_CRABBOX_PRIVACY_PATHS: JSON.stringify([
"private-canary.txt",
"tracked.ignored",
]),
},
]) {
rmSync(capturedBundle, { force: true });
const rejected = runCommand(
process.execPath,
[fixtureWrapper, "run", "--provider", provider, "--", "node", sourceCommand],
{ cwd: producer, env: { ...env, ...extraEnv }, encoding: "utf8", timeout: 10_000 },
);
expectRejectedBeforeUpload(rejected);
}
for (const mutation of ["mode", "bytes", "deletion"]) {
const preload = path.join(root, `mutate-${mutation}.cjs`);
writeFileSync(
preload,
`const fs = require("node:fs"); const chmod = fs.chmodSync; fs.chmodSync = (file, mode) => { chmod(file, mode); if (file === "mode.sh") { ${mutation === "mode" ? "chmod(file, mode & ~0o100)" : mutation === "deletion" ? 'fs.writeFileSync("committed-deleted.ignored", "stale restored source")' : 'fs.appendFileSync(file, "corrupted after materialization")'}; } };`,
);
const rejected = receive(
`receiver-${mutation}-mismatch`,
candidate.remoteCommand,
candidate.bundle,
origin,
{ NODE_OPTIONS: `--require=${preload}` },
);
expect(rejected.result.status, failureDetail(rejected.result)).toBe(2);
expect(rejected.result.stderr).toContain(`source ${mutation} mismatch`);
expect(rejected.result.stdout).not.toContain("transport fixture reached");
expect(git(rejected.receiver, ["rev-parse", "HEAD"])).toBe(base);
}
const reserved = path.join(producer, ".openclaw-crabbox-changed-gate.bundle");
const victim = path.join(external, "canary.txt");
symlinkSync(victim, reserved);
rmSync(capturedBundle, { force: true });
const reservedRejection = runCommand(
process.execPath,
[fixtureWrapper, "run", "--provider", provider, "--", "node", sourceCommand],
{ cwd: producer, env, encoding: "utf8", timeout: 10_000 },
);
expectRejectedBeforeUpload(reservedRejection);
expect(readFileSync(victim, "utf8")).toBe("external referent must stay local\n");
rmSync(reserved);
const emptyOrigin = path.join(root, "empty-origin");
mkdirSync(emptyOrigin);
git(emptyOrigin, ["init", "-q", "--bare"]);
const missingBase = receive(
"missing-base",
candidate.remoteCommand,
candidate.bundle,
emptyOrigin,
);
expect(missingBase.result.status, failureDetail(missingBase.result)).toBe(2);
expect(missingBase.result.stdout).not.toContain("transport fixture reached");
const blob = git(producer, ["rev-parse", "HEAD:mode.sh"]);
const invalidPathEntry = Buffer.concat([
Buffer.from(`100644 ${blob}\tinvalid-`),
Buffer.from([255]),
Buffer.from(".txt\0"),
]);
const indexed = runCommand("git", ["update-index", "-z", "--index-info"], {
cwd: producer,
env,
encoding: "utf8",
input: invalidPathEntry,
timeout: 10_000,
});
expect(indexed.status, failureDetail(indexed)).toBe(0);
rmSync(capturedBundle, { force: true });
const invalidPathRejection = runCommand(
process.execPath,
[fixtureWrapper, "run", "--provider", provider, "--", "node", sourceCommand],
{ cwd: producer, env, encoding: "utf8", timeout: 10_000 },
);
expectRejectedBeforeUpload(invalidPathRejection);
}
},
);
it.runIf(process.platform === "win32")(
"selects a real source capsule through a Crabbox .cmd shim",
() => {
const root = invocationLogTempDirs.make("openclaw-capsule-cmd-");
const producer = path.join(root, "producer");
const capturedBundle = path.join(root, "captured.bundle");
const invocationLog = path.join(root, "invocations.jsonl");
const fakeBin = makeFakeCrabbox(defaultProviderHelp);
expect(existsSync(path.join(fakeBin, "crabbox.cmd"))).toBe(true);
const emptyGitConfig = path.join(root, "empty.gitconfig");
writeFileSync(emptyGitConfig, "");
const env = {
...testHomeEnv(path.join(root, "home")),
SystemRoot: process.env.SystemRoot,
ComSpec: process.env.ComSpec,
PATH: [fakeBin, path.dirname(process.execPath), process.env.PATH ?? ""].join(
path.delimiter,
),
GIT_CONFIG_GLOBAL: emptyGitConfig,
GIT_CONFIG_NOSYSTEM: "1",
GIT_AUTHOR_NAME: "Transport fixture",
GIT_AUTHOR_EMAIL: "transport@example.invalid",
GIT_COMMITTER_NAME: "Transport fixture",
GIT_COMMITTER_EMAIL: "transport@example.invalid",
OPENCLAW_CRABBOX_WRAPPER_IGNORE_REPO_BINARY: "1",
OPENCLAW_CRABBOX_SYNC_TMPDIR: path.join(root, "sync"),
OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES: "0",
OPENCLAW_FAKE_CRABBOX_COPY_CHANGED_GATE_BUNDLE_TO: capturedBundle,
OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG: invocationLog,
};
mkdirSync(producer);
const git = (args: string[]) => {
const result = spawnSync("git", args, {
cwd: producer,
env,
encoding: "utf8",
timeout: 10_000,
});
expect(result.status, result.stderr).toBe(0);
return result.stdout.trim();
};
git(["init", "-q", "-b", "main"]);
git(["remote", "add", "origin", producer]);
writeFileSync(path.join(producer, ".gitignore"), ".tmp/\n");
writeFileSync(
path.join(producer, "package.json"),
JSON.stringify({ packageManager: "pnpm@12.0.0" }),
);
git(["add", "-A"]);
git(["commit", "-qm", "base"]);
git(["update-ref", "refs/remotes/origin/main", git(["rev-parse", "HEAD"])]);
const wrapper = path.join(producer, ".tmp", "crabbox-wrapper.mjs");
mkdirSync(path.dirname(wrapper));
copyFileSync(realBundledWrapperPath, wrapper);
const result = spawnSync(
process.execPath,
[wrapper, "run", "--provider", "aws", "--target", "linux", "--", "pnpm", "check:changed"],
{ cwd: producer, env, encoding: "utf8", timeout: 10_000 },
);
const run = expectSuccessfulWrapperRun(result);
expect(readInvocations(invocationLog)).toContainEqual([
"sync-plan",
"--json",
"--limit",
"2147483647",
]);
expect(readFileSync(capturedBundle).length).toBeGreaterThan(0);
expect(git(["bundle", "list-heads", capturedBundle])).toMatch(
/ refs\/openclaw\/source-capsule$/u,
);
expect(existsSync(run.output.cwd)).toBe(false);
expect(readdirSync(path.join(root, "sync"))).toEqual([]);
},
);
it("bootstraps Git metadata for env-prefixed sparse changed gates", () => {
const { output, remoteCommand } = runSuccessfulDefaultWrapper(
[
"run",
"--provider",
"aws",
"--",
"env",
"OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1",
"OPENCLAW_CHANGED_LANES_RAW_SYNC=1",
"CI=1",
"corepack",
"pnpm",
"check:changed",
],
sparseChangedGateOptions,
);
expect(output.args).toContain("--shell");
expect(remoteCommand).toContain(remoteChangedGateFetch);
expect(remoteCommand).toMatch(
/; env OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 CI=1 corepack pnpm check:changed$/u,
);
});
it("preserves macOS JS bootstrapping for sparse changed gates on remote raw syncs", () => {
const { output, remoteCommand } = runSuccessfulMacosCommand(
["pnpm", "check:changed"],
sparseChangedGateOptions,
);
expect(output.args.filter((arg) => arg === "--shell")).toHaveLength(1);
expect(remoteCommand).toContain(remoteChangedGateFetch);
expect(remoteCommand.indexOf("node --version >&2 || return 1")).toBeLessThan(
remoteCommand.indexOf("node -e"),
);
expect(remoteCommand.indexOf("corepack enable --install-directory")).toBeLessThan(
remoteCommand.indexOf("node -e"),
);
expect(remoteCommand.indexOf("node -e")).toBeLessThan(
remoteCommand.indexOf("pnpm --version >&2"),
);
expectMacosJsBootstrap(
remoteCommand,
`openclaw_crabbox_env ${remoteChangedGateEnvPrefix} pnpm check:changed`,
);
});
it("preserves macOS JS and Git bootstraps for sparse shell changed gates with setup", () => {
const shellScript = ["set -euo pipefail", "pnpm check:changed"].join("\n");
const { output, remoteCommand } = runSuccessfulMacosShell(
shellScript,
sparseChangedGateOptions,
);
expect(output.args.filter((arg) => arg === "--shell")).toHaveLength(1);
expect(remoteCommand).toContain("node -e");
expectMacosJsBootstrap(remoteCommand, `${remoteChangedGateExport} ${shellScript}`);
});
it("preserves macOS JS and Git bootstraps for shell-wrapped sparse changed gates", () => {
const shellScript = "bash -lc 'pnpm check:changed'";
const { remoteCommand } = runSuccessfulMacosShell(shellScript, sparseChangedGateOptions);
expect(remoteCommand).toContain("node -e");
expectMacosJsBootstrap(remoteCommand, `${remoteChangedGateExport} ${shellScript}`);
});
it("does not mistake quoted remote-child markers for shell changed-gate environment", () => {
const shellScript = 'echo "OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1"; pnpm check:changed';
const { remoteCommand } = runSuccessfulMacosShell(shellScript, sparseChangedGateOptions);
expect(remoteCommand).toContain(remoteChangedGateFetch);
expectGroupedShellCommand(remoteCommand, `${remoteChangedGateExport} ${shellScript}`);
});
it.each([
{
name: "preserves sparse changed-gate Git bootstrap for assignment-prefix command substitutions",
shellScript: "TOOL_ROOT=$(pwd) pnpm check:changed",
},
{
name: "preserves sparse changed-gate Git bootstrap for command-prefixed shell commands",
shellScript: "command pnpm check:changed",
},
{
expectFetch: true,
name: "preserves sparse changed-gate Git bootstrap for bash -lc shell commands",
shellScript:
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 bash -lc 'set -euo pipefail; pnpm check:changed'",
},
{
name: "preserves sparse changed-gate Git bootstrap for shell option values before -c",
shellScript: "bash -o pipefail -c 'pnpm check:changed'",
},
{
name: "preserves sparse changed-gate Git bootstrap for grouped shell options before -c",
shellScript: "bash -eo pipefail -c 'pnpm check:changed'",
},
{
name: "preserves sparse changed-gate Git bootstrap for absolute time-prefixed shell commands",
shellScript: "/usr/bin/time -l pnpm check:changed",
},
])("$name", ({ expectFetch, shellScript }) => {
const { remoteCommand } = runSparseShell(shellScript);
expect(remoteCommand).toContain("node -e");
if (expectFetch) {
expect(remoteCommand).toContain(remoteChangedGateFetch);
}
expect(remoteCommand).toContain(`; ${remoteChangedGateExport} ${shellScript}`);
});
it.each([
{
target: "node",
command: [
"timeout",
"1200s",
"node",
"scripts/check-changed.mjs",
"--base",
"origin/main",
"--head",
"HEAD",
],
suffix:
/; env OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 CI=1 timeout 1200s node scripts\/check-changed\.mjs --base origin\/main --head HEAD$/u,
},
{
target: "shell",
command: ["timeout", "1200s", "bash", "-lc", "pnpm check:changed"],
suffix:
/; env OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 CI=1 timeout 1200s bash -lc 'pnpm check:changed'$/u,
},
{
target: "env -i",
command: ["env", "-i", "pnpm", "check:changed"],
suffix:
/; env -i OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 CI=1 pnpm check:changed$/u,
},
{
target: "absolute env -i",
command: ["/usr/bin/env", "-i", "pnpm", "check:changed"],
suffix:
/; \/usr\/bin\/env -i OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 CI=1 pnpm check:changed$/u,
},
])("preserves direct $target changed gates after Git bootstrap", ({ command, suffix }) => {
const { output, remoteCommand } = runSuccessfulDefaultWrapper(
["run", "--provider", "aws", "--", ...command],
sparseChangedGateOptions,
);
expect(output.args).toContain("--shell");
expect(remoteCommand).toContain("node -e");
expect(remoteCommand).toMatch(suffix);
});
it.each([
{
command: ["./tools/env", "-i", "pnpm", "check:changed"],
name: "does not mark custom env executables outside the sanitized env",
},
{
command: ["FOO=1", "env", "-i", "pnpm", "check:changed"],
name: "does not mark assignment-prefixed env -i changed gates outside the sanitized env",
},
{
command: ["timeout", "1200s", "env", "-i", "CI=1", "pnpm", "check:changed"],
name: "does not mark timeout-prefixed env -i changed gates outside the sanitized env",
},
{
command: ["env", "env", "-i", "pnpm", "check:changed"],
name: "does not mark nested env -i changed gates outside the sanitized env",
},
{
command: ["--shell", "--", "bash -lc 'env -i CI=1 pnpm check:changed'"],
name: "does not mark shell env -i changed gates outside the sanitized env",
shell: true,
},
])("$name", ({ command, shell }) => {
const result = runDefaultWrapper(
["run", "--provider", "aws", ...(shell ? command : ["--", ...command])],
sparseChangedGateOptions,
);
const output = parseFakeCrabboxOutput(result);
const renderedCommand = normalizeShellLineEndings(output.args.at(-1) ?? "");
expect(result.status).toBe(0);
expect(renderedCommand).not.toContain("OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1");
expect(renderedCommand).toContain(".openclaw-crabbox-changed-gate.bundle");
expect(renderedCommand.endsWith(`; ${shell ? command.at(-1) : command.join(" ")}`)).toBe(true);
});
it.each([
{
name: "does not treat quoted sparse shell text as a changed gate",
shellScript: 'cat <<EOF\npnpm check:changed\nEOF\necho "docs; pnpm check:changed"',
},
{
name: "does not treat escaped heredoc bodies as changed gates",
shellScript: "cat <<\\EOF\npnpm check:changed\nEOF\necho done",
},
{
name: "does not treat nested heredoc bodies in substitutions as changed gates",
shellScript: 'echo "$(cat <<EOF\npnpm check:changed\nEOF\n)"',
},
])("$name", ({ shellScript }) => {
const { remoteCommand } = runSparseShell(shellScript);
expect(remoteCommand).toContain(".openclaw-crabbox-changed-gate.bundle");
expect(remoteCommand).not.toContain("OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1");
expect(remoteCommand.endsWith(`; ${shellScript}`)).toBe(true);
});
it("detects JavaScript commands after hyphenated heredoc delimiters", () => {
const shellScript = "cat <<EOF-JSON\nnode is literal\nEOF-JSON\npnpm --version";
const { remoteCommand } = runSuccessfulMacosShell(shellScript);
expectMacosJsBootstrap(remoteCommand, shellScript);
});
it("bootstraps raw AWS macOS shell scripts for unquoted heredoc command substitutions", () => {
const shellScript = "cat <<EOF\n$(pnpm --version)\nEOF";
const { remoteCommand } = runSuccessfulMacosShell(shellScript);
expectMacosJsBootstrap(remoteCommand, shellScript);
});
it("keeps quoted heredoc command substitutions literal", () => {
const shellScript = "cat <<'EOF'\n$(pnpm --version)\nEOF";
const { remoteCommand } = runSuccessfulMacosShell(shellScript);
expect(remoteCommand).not.toContain("openclaw_crabbox_bootstrap_macos_js");
});
it("preserves existing shell changed-gate commands after remote Git bootstrap", () => {
const { output, remoteCommand } = runSuccessfulDefaultWrapper(
["run", "--provider", "aws", "--shell", "--", "env CI=1 pnpm check:changed"],
sparseChangedGateOptions,
);
expect(output.args.filter((arg) => arg === "--shell")).toHaveLength(1);
expect(remoteCommand).toContain(remoteChangedGateFetch);
expect(remoteCommand).toMatch(
/; export OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 CI=1; env CI=1 pnpm check:changed$/u,
);
});
it("does not inject the POSIX changed-gate bootstrap for Windows targets", () => {
const { output } = runSuccessfulDefaultWrapper(
[
"run",
"--provider",
"aws",
"--target",
"windows",
"--",
"corepack",
"pnpm",
"check:changed",
],
sparseChangedGateOptions,
);
expect(output.args).not.toContain("--shell");
expect(output.args).toEqual([
"run",
"--provider",
"aws",
"--target",
"windows",
"--",
"corepack",
"pnpm",
"check:changed",
]);
});
it("creates sparse-sync temporary full checkouts under the durable cache root", () => {
withSparseSyncRoot(".crabbox-test-sync-root", {}, ({ result, syncRoot }) => {
const { output } = expectSuccessfulWrapperRun(result);
expect(output.cwd).toContain(`${syncRoot}${path.sep}openclaw-crabbox-sync-`);
expect(readdirSync(syncRoot)).toEqual([]);
});
});
it("keeps overlapping sparse-sync fixtures from deleting each other's files", () => {
const name = ".crabbox-test-isolation-sync-root";
withSparseSyncRoot(name, {}, ({ result, syncRoot }) => {
expectSuccessfulWrapperRun(result);
const marker = path.join(syncRoot, "active-fixture.txt");
writeFileSync(marker, "owned by the outer fixture");
withSparseSyncRoot(name, {}, ({ result: innerResult }) => {
expectSuccessfulWrapperRun(innerResult);
expect(readFileSync(marker, "utf8")).toBe("owned by the outer fixture");
});
expect(readFileSync(marker, "utf8")).toBe("owned by the outer fixture");
});
});
it("fails sparse-sync full checkout early when the sync root is too low on disk", () => {
withSparseSyncRoot(
".crabbox-test-low-disk-sync-root",
{ OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES: "999999999999999" },
({ result, syncRoot }) => {
expect(result.status).toBe(1);
expect(result.stderr).toContain(
"insufficient free disk for Crabbox sparse-sync full checkout",
);
expect(result.stderr).toContain("OPENCLAW_CRABBOX_SYNC_TMPDIR");
expect(result.stderr).toContain("OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES");
expect(readdirSync(syncRoot)).toEqual([]);
},
);
});
it.each([
{
root: ".crabbox-test-invalid-disk-sync-root",
key: "OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES",
value: "1024mb",
error:
'OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES must be a non-negative integer byte count, got "1024mb"',
},
{
root: ".crabbox-test-unsafe-disk-sync-root",
key: "OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES",
value: String(Number.MAX_SAFE_INTEGER + 1),
error: "OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES must be a safe non-negative integer byte count",
},
{
root: ".crabbox-test-invalid-keepalive-sync-root",
key: "OPENCLAW_CRABBOX_SYNC_KEEPALIVE_MS",
value: "10ms",
error:
'OPENCLAW_CRABBOX_SYNC_KEEPALIVE_MS must be a non-negative integer millisecond interval, got "10ms"',
},
])("rejects invalid sparse-sync limits: $key=$value", ({ root, key, value, error }) => {
withSparseSyncRoot(root, { [key]: value }, ({ result, syncRoot }) => {
expect(result.status).toBe(1);
expect(result.stderr).toContain(error);
expect(readdirSync(syncRoot)).toEqual([]);
});
});
(process.platform === "win32" ? it.skip : it).each(["node", "pnpm"] as const)(
"terminates Crabbox descendants before parent signal exit through %s",
async (entrypoint) => {
await runWrapperCleanupProof({ kind: "signal", entrypoint, repeated: false });
},
25_000,
);
it.skipIf(process.platform === "win32")(
"reports the readiness deadline and last phase without inventing a writer failure",
async () => {
let failure: Error | undefined;
try {
await runWrapperCleanupProof({ kind: "readiness" });
} catch (error) {
if (!(error instanceof Error)) {
throw error;
}
failure = error;
}
if (!failure) {
throw new Error("held wrapper unexpectedly became ready");
}
const retained = /retained fixture (.+) and output /u.exec(failure.message)?.[1];
expect(retained).toBeDefined();
if (!retained) {
throw failure;
}
try {
expect(failure.message).toContain(
"wrapper readiness not observed within 8 s; last observed phase loading wrapper",
);
expect(failure.message).not.toContain("fixture teardown could not be verified");
expect(failure.message).not.toContain("fixture writers did not settle");
expect(failure.message).not.toContain("fixture ownership receipts are incomplete");
expect(existsSync(path.join(retained, "run.spawned"))).toBe(false);
} finally {
// This injected preload cannot spawn children; join both recorded owners before disposal.
const phases: WrapperReadinessPhase[] = JSON.parse(
readFileSync(path.join(retained, "readiness-phases.json"), "utf8"),
);
expect(phases.at(-1)?.phase).toBe("loading wrapper");
expect(phases.filter(({ pid }) => pid).every(({ pid }) => !isProcessAlive(pid!))).toBe(
true,
);
rmSync(retained, { recursive: true, force: true });
}
},
25_000,
);
(process.platform === "win32" ? it.skip : it).each(["node", "pnpm"] as const)(
"keeps cleanup active after repeated parent signals through %s",
async (entrypoint) => {
await runWrapperCleanupProof({ kind: "signal", entrypoint, repeated: true });
},
25_000,
);
(process.platform === "win32" ? it.skip : it)(
"preserves graceful cancellation artifacts through pnpm terminal",
async () => {
await runWrapperCleanupProof({
kind: "signal",
entrypoint: "pnpm",
repeated: false,
cooperative: true,
});
},
25_000,
);
it.skipIf(process.platform === "win32")(
"cleans preparation cancellation after staging allocation without starting a run",
async () => {
await runWrapperCleanupProof({ kind: "preparation" });
},
25_000,
);
it.skipIf(process.platform === "win32")(
"reports capsule cleanup failure during preparation cancellation without starting a run",
async () => {
await runWrapperCleanupProof({ kind: "preparation", cleanupFails: true });
},
25_000,
);
it.skipIf(process.platform === "win32").each(["macos", "capsule"] as const)(
"cancels blocked wrapper stdin for %s without starting a run",
async (target) => {
await runWrapperCleanupProof({ kind: "stdin", target });
},
25_000,
);
it.skipIf(process.platform === "win32")(
"retains source, claim and artifacts for escaped stderr until its writer is rescued",
async () => {
await runWrapperCleanupProof({ kind: "escaped" });
},
25_000,
);
it.skipIf(process.platform === "win32").each([
{ target: "script", exitCode: 0 },
{ target: "script", exitCode: 23 },
{ target: "source", exitCode: 0 },
{ target: "source", exitCode: 23 },
] as const)(
"reports $target removal failure without skipping independent cleanup (exit=$exitCode)",
async ({ target, exitCode }) => {
await runWrapperCleanupProof({ kind: "removal", target, exitCode });
},
25_000,
);
(process.platform === "win32" ? it.skip : it)(
"terminates when sparse-sync temporary full checkouts disappear while Crabbox is running",
() => {
const result = runDefaultWrapper(["run", "--provider", "aws", "--", "echo ok"], {
env: {
OPENCLAW_CRABBOX_SYNC_KEEPALIVE_MS: "10",
OPENCLAW_FAKE_CRABBOX_DELETE_CWD_ONCE: "1",
},
gitResponses: {
[GIT_CONFIG_SPARSE_KEY]: { stdout: "true\n" },
[GIT_STATUS_PORCELAIN_KEY]: { stdout: "" },
},
});
expect(result.status).not.toBe(0);
expect(result.stderr).toContain(
"temporary full checkout disappeared while Crabbox was running",
);
expect(result.stderr).toContain("child cwd cannot be repaired");
},
);
(process.platform === "win32" ? it.skip : it)(
"fails successful sparse-sync children when their temporary full checkout vanishes before exit",
() => {
const result = runDefaultWrapper(["run", "--provider", "aws", "--", "echo ok"], {
env: {
OPENCLAW_CRABBOX_SYNC_KEEPALIVE_MS: "60000",
OPENCLAW_FAKE_CRABBOX_DELETE_CWD_AND_EXIT: "1",
},
gitResponses: {
[GIT_CONFIG_SPARSE_KEY]: { stdout: "true\n" },
[GIT_STATUS_PORCELAIN_KEY]: { stdout: "" },
},
});
expect(result.status).toBe(1);
expect(result.stderr).toContain(
"temporary full checkout vanished before Crabbox finished syncing",
);
},
);
it("keeps local artifact paths rooted at the original checkout", () => {
const { output } = runSuccessfulDefaultWrapper(
[
"run",
"--provider",
"blacksmith-testbox",
"--blacksmith-ref",
"main",
"--capture-stdout=.artifacts/stdout.log",
"--capture-stderr",
".artifacts/stderr.log",
"--download",
"/tmp/proof=.artifacts/proof",
"--",
"echo ok",
],
cleanSparseSyncOptions,
);
expect(output.cwd).toContain("openclaw-crabbox-sync-");
expect(output.args).toContain(
`--capture-stdout=${path.join(repoRoot, ".artifacts/stdout.log")}`,
);
expect(output.args).toContain(path.join(repoRoot, ".artifacts/stderr.log"));
expect(output.args).toContain(`/tmp/proof=${path.join(repoRoot, ".artifacts/proof")}`);
});
it.each([
{ mode: "capsule", artifacts: "captures", exitCode: 23, fault: undefined },
{ mode: "capsule", artifacts: "both", exitCode: 23, fault: undefined },
{ mode: "sparse", artifacts: "both", exitCode: 23, fault: undefined },
{ mode: "capsule", artifacts: "runs", exitCode: 0, fault: undefined },
{ mode: "capsule", artifacts: "none", exitCode: 0, fault: undefined },
{ mode: "direct", artifacts: "captures", exitCode: 23, fault: undefined },
...[0, 23].map((exitCode) => ({
mode: "capsule",
artifacts: "both",
exitCode,
fault: "destination file",
})),
...[0, 23].map((exitCode) => ({
mode: "capsule",
artifacts: "both",
exitCode,
fault: "claim restoration",
})),
...(process.platform === "win32"
? []
: [
"source root link",
"source root dangling link",
"source captures link",
"nested file link",
"destination root link",
"destination root dangling link",
"destination parent link",
"destination parent dangling link",
"nested fifo",
]
).map((fault) => ({ mode: "capsule", artifacts: "both", exitCode: 0, fault })),
])(
"retains native artifacts through the wrapper ($mode, $artifacts, exit=$exitCode, fault=$fault)",
({ mode, artifacts, exitCode, fault }) => {
const root = realpathSync(artifactTempDirs.make("openclaw-wrapper-artifacts-"));
const producer = path.join(root, "repo");
const syncRoot = path.join(root, "sync");
const fixtureWrapper = path.join(producer, ".tmp", "crabbox-wrapper.mjs");
mkdirSync(path.dirname(fixtureWrapper), { recursive: true });
copyFileSync(realBundledWrapperPath, fixtureWrapper);
const env = {
...testHomeEnv(path.join(root, "home")),
XDG_STATE_HOME: path.join(root, "home", ".local", "state"),
...(process.platform === "win32" ? { SystemRoot: process.env.SystemRoot } : {}),
PATH: [makeFakeCrabbox(defaultProviderHelp), process.env.PATH ?? ""].join(path.delimiter),
GIT_CONFIG_GLOBAL: "/dev/null",
GIT_CONFIG_NOSYSTEM: "1",
GIT_AUTHOR_NAME: "Artifact fixture",
GIT_AUTHOR_EMAIL: "fixture@example.invalid",
GIT_COMMITTER_NAME: "Artifact fixture",
GIT_COMMITTER_EMAIL: "fixture@example.invalid",
CRABBOX_PROVIDER: "",
CRABBOX_TARGET: "",
CRABBOX_TARGET_OS: "",
CRABBOX_WINDOWS_MODE: "",
OPENCLAW_CRABBOX_WRAPPER_IGNORE_REPO_BINARY: "1",
OPENCLAW_CRABBOX_SYNC_TMPDIR: syncRoot,
OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES: "0",
OPENCLAW_FAKE_CRABBOX_RUN_STATUS: String(exitCode),
};
const git = (...args: string[]) => {
const result = spawnSync("git", args, { cwd: producer, env, encoding: "utf8" });
expect(result.status, result.stderr).toBe(0);
return result.stdout.trim();
};
git("init", "-q", "-b", "main");
writeFileSync(path.join(producer, ".gitignore"), ".tmp/\n.crabbox\n");
writeFileSync(path.join(producer, "fixture.txt"), "source fixture\n");
git("add", ".gitignore", "fixture.txt");
git("commit", "-qm", "fixture");
git("remote", "add", "origin", producer);
git("update-ref", "refs/remotes/origin/main", "HEAD");
if (mode === "sparse") {
git("sparse-checkout", "set", "--no-cone", "/fixture.txt");
}
const capturePath = ".crabbox/captures/tbx_fixture-20260904T190427Z.tar.gz";
const runPath = ".crabbox/runs/run_fixture/proof/nested.json";
const bundleInput = path.join(root, "bundle-input");
mkdirSync(bundleInput);
const logBytes = Buffer.from("synthetic failure\r\n\u001b[31mraw diagnostic\u001b[0m\n");
writeFileSync(path.join(bundleInput, "stdout.log"), logBytes);
const bundle = path.join(root, "failure.tar.gz");
const packed = spawnSync("tar", ["-czf", bundle, "-C", bundleInput, "stdout.log"], { env });
expect(packed.status).toBe(0);
const bytes = readFileSync(bundle);
const files = [
...(artifacts === "captures" || artifacts === "both" ? [capturePath] : []),
...(artifacts === "runs" || artifacts === "both" ? [runPath] : []),
];
const previousFile = path.join(
producer,
mode === "direct" ? ".crabbox/captures/older.tgz" : capturePath,
);
mkdirSync(path.dirname(previousFile), { recursive: true });
writeFileSync(previousFile, "prior evidence\n");
const retainedRoot = path.join(producer, ".crabbox", "wrapper-artifacts");
const outside = path.join(root, "outside");
mkdirSync(outside);
const sentinel = "synthetic-private-data-must-not-be-printed";
const sentinelPath = path.join(outside, "private.txt");
writeFileSync(sentinelPath, sentinel);
const artifactLinks: Record<string, string> = {};
const missing = path.join(root, "missing");
if (fault === "destination file") {
writeFileSync(retainedRoot, "not a directory\n");
} else if (fault?.startsWith("destination")) {
const target = fault.includes("dangling") ? missing : outside;
if (fault.includes("root")) {
renameSync(path.join(producer, ".crabbox"), path.join(root, "prior-crabbox"));
symlinkSync(target, path.join(producer, ".crabbox"), "dir");
} else {
symlinkSync(target, retainedRoot, "dir");
}
} else if (fault?.startsWith("source")) {
const file = fault.includes("root") ? ".crabbox" : ".crabbox/captures";
artifactLinks[file] = fault.includes("dangling") ? missing : outside;
} else if (fault?.startsWith("nested") && fault !== "nested fifo") {
artifactLinks[".crabbox/captures/linked-artifact"] = sentinelPath;
}
const retainedDirectories: string[] = [];
const attempts = mode === "capsule" && artifacts === "both" && !fault ? 2 : 1;
const emittedFiles = fault?.startsWith("source") && fault.includes("dangling") ? [] : files;
const ignoredFiles = [".crabbox/credentials/token", ".crabbox/state/claim", ".crabbox/env"];
for (let attempt = 0; attempt < attempts; attempt += 1) {
const result = spawnSync(
process.execPath,
[
fixtureWrapper,
"run",
"--provider",
mode === "capsule" ? "blacksmith-testbox" : "local-container",
...(fault === "claim restoration" ? ["--keep", "--timing-json"] : []),
"--",
"false",
],
{
cwd: producer,
env: {
...env,
OPENCLAW_FAKE_CRABBOX_ARTIFACT_LINKS: JSON.stringify(artifactLinks),
...(fault === "claim restoration"
? {
OPENCLAW_FAKE_CRABBOX_CLAIM_PATH: path.join(
env.XDG_STATE_HOME,
"crabbox",
"claims",
"tbx_fixture.json",
),
OPENCLAW_FAKE_CRABBOX_TIMING_LEASE_ID: "tbx_fixture",
OPENCLAW_FAKE_CRABBOX_CORRUPT_CLAIM: "1",
}
: {}),
...(fault === "nested fifo"
? { OPENCLAW_FAKE_CRABBOX_ARTIFACT_FIFO: ".crabbox/captures/pipe" }
: {}),
OPENCLAW_FAKE_CRABBOX_ARTIFACTS: JSON.stringify(
Object.fromEntries([
...emittedFiles.map((file) => [file, bytes.toString("base64")]),
...(fault?.includes("dangling") && fault.startsWith("source")
? []
: ignoredFiles.map((file) => [file, Buffer.from(sentinel).toString("base64")])),
]),
),
},
encoding: "utf8",
timeout: 10_000,
},
);
expect(result.error, result.stderr).toBeUndefined();
expect(result.stdout, result.stderr).not.toBe("");
const output = parseFakeCrabboxOutput(result);
const previousPath = fault?.startsWith("destination root")
? path.join(root, "prior-crabbox", path.relative(".crabbox", capturePath))
: previousFile;
expect(readFileSync(previousPath, "utf8")).toBe("prior evidence\n");
expect(readFileSync(sentinelPath, "utf8")).toBe(sentinel);
expect(result.stdout + result.stderr).not.toContain(sentinel);
if (fault) {
expect(result.status, result.stderr).toBe(exitCode || 1);
expect(result.stderr).toContain("temporary checkout retained");
expect(result.stderr).toContain(output.cwd);
expect(existsSync(output.cwd)).toBe(true);
for (const file of emittedFiles) {
expect(readFileSync(path.join(output.cwd, file))).toEqual(bytes);
}
if (fault === "claim restoration") {
expect(result.stderr).toContain("lease ownership restoration failed");
expect(result.stderr).toContain("preserved temporary artifacts:");
const retained = readdirSync(retainedRoot);
expect(retained).toHaveLength(1);
for (const file of emittedFiles) {
expect(
readFileSync(
path.join(retainedRoot, retained[0]!, path.relative(".crabbox", file)),
),
).toEqual(bytes);
}
} else if (fault === "destination file") {
expect(readFileSync(retainedRoot, "utf8")).toBe("not a directory\n");
} else if (fault.startsWith("destination")) {
expect(readdirSync(outside)).toEqual(["private.txt"]);
expect(existsSync(missing)).toBe(false);
} else {
expect(existsSync(retainedRoot) ? readdirSync(retainedRoot) : []).toEqual([]);
}
if (fault !== "claim restoration") {
expect(result.stderr).not.toContain("preserved temporary artifacts:");
}
continue;
}
expect(result.status, result.stderr).toBe(exitCode);
if (mode === "direct") {
expect(output.cwd).toBe(producer);
expect(existsSync(retainedRoot)).toBe(false);
for (const file of files) {
expect(readFileSync(path.join(producer, file))).toEqual(bytes);
}
} else {
expect(output.cwd).not.toBe(producer);
expect(existsSync(output.cwd)).toBe(false);
if (mode === "capsule" && process.platform !== "win32") {
expectMirrorDirectories(syncRoot, producer);
} else {
expect(readdirSync(syncRoot)).toEqual([]);
}
if (files.length === 0) {
expect(existsSync(retainedRoot)).toBe(false);
} else {
const relocated = result.stderr.match(/preserved temporary artifacts: (.+) -> (.+)/u);
expect(relocated?.[1]).toBe(path.join(output.cwd, ".crabbox"));
const retained = path.resolve(producer, relocated![2]!);
expect(retainedDirectories).not.toContain(retained);
retainedDirectories.push(retained);
for (const directory of retainedDirectories) {
if (process.platform !== "win32") {
expect(lstatSync(directory).mode & 0o777).toBe(0o700);
}
expect(new Set(readdirSync(directory))).toEqual(
new Set(files.map((file) => file.split("/")[1])),
);
for (const file of files) {
const retainedFile = path.join(directory, path.relative(".crabbox", file));
expect(readFileSync(retainedFile)).toEqual(bytes);
expect(lstatSync(retainedFile).isFile()).toBe(true);
if (file === capturePath) {
const unpacked = spawnSync("tar", ["-xOf", retainedFile, "stdout.log"], { env });
expect(unpacked.status).toBe(0);
expect(unpacked.stdout).toEqual(logBytes);
}
if (process.platform !== "win32") {
expect(lstatSync(retainedFile).mode & 0o777).toBe(0o600);
expect(lstatSync(path.dirname(retainedFile)).mode & 0o777).toBe(0o700);
expect(lstatSync(retainedFile).uid).toBe(process.getuid?.());
}
}
}
}
}
}
expect(git("status", "--porcelain")).toBe("");
},
);
it("uses the temporary full checkout for sparse sync-only runs", () => {
const { output, result } = runSuccessfulDefaultWrapper(
[
"run",
"--provider",
"blacksmith-testbox",
"--blacksmith-ref",
"feature-branch",
"--sync-only",
],
cleanSparseSyncOptions,
);
expect(result.stderr).toContain("syncing from temporary full checkout");
expect(output.cwd).toContain("openclaw-crabbox-sync-");
});
});