fix(scripts): timeout crabbox wrapper sanity checks

Add bounded timeouts for Crabbox wrapper sanity probes so a stale or hung selected binary cannot block the wrapper indefinitely. The wrapper now maps timed-out sanity probes to a deterministic failure and keeps provider/help parsing behavior intact.

Also add regression coverage for a binary whose `--version` probe hangs while `run --help` still responds.

Co-authored-by: Evan Newman <evanjames010101@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Evan Newman
2026-05-31 06:59:12 -02:30
committed by GitHub
parent 70c8abdca1
commit a0d2febe6b
2 changed files with 37 additions and 1 deletions

View File

@@ -175,9 +175,12 @@ function checkedOutput(command, commandArgs) {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
windowsVerbatimArguments: invocation.windowsVerbatimArguments,
timeout: 5_000,
killSignal: "SIGKILL",
});
const timedOut = result.error?.name === "Error" && result.signal === "SIGKILL";
return {
status: result.status ?? 1,
status: timedOut ? 124 : (result.status ?? 1),
text: `${result.stdout ?? ""}${result.stderr ?? ""}`.trim(),
stdout: (result.stdout ?? "").trim(),
};

View File

@@ -137,6 +137,27 @@ function writeFakeCrabbox(binDir: string, helpText: string): string {
return crabboxPath;
}
function makeSlowVersionCrabbox(helpText: string): string {
const binDir = mkdtempSync(path.join(tmpdir(), "openclaw-slow-crabbox-"));
tempDirs.push(binDir);
const crabboxPath = path.join(binDir, "crabbox");
const script = [
"#!/usr/bin/env node",
"const args = process.argv.slice(2);",
'if (args[0] === "--version") { setTimeout(() => process.exit(0), 6_000); }',
`else if (args[0] === "run" && args[1] === "--help") { process.stdout.write(${JSON.stringify(helpText)}); }`,
].join("\n");
writeFileSync(crabboxPath, `${script}\n`, "utf8");
writeFileSync(
`${crabboxPath}.cmd`,
`@echo off\r\n"${process.execPath}" "%~dp0crabbox" %*\r\n`,
"utf8",
);
chmodSync(crabboxPath, 0o755);
return binDir;
}
function shellSingleQuote(value: string): string {
return `'${value.replaceAll("'", "'\\''")}'`;
}
@@ -1599,6 +1620,18 @@ describe.concurrent("scripts/crabbox-wrapper", () => {
expect(result.stderr).toContain("selected binary does not advertise provider bogus");
});
it("times out hung sanity probes before rejecting the selected binary", () => {
const helpText = "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n";
const result = runWrapper(helpText, ["--version"], {
extraPathEntries: [makeSlowVersionCrabbox(helpText)],
});
expect(result.error).toBeUndefined();
expect(result.status).toBe(2);
expect(result.stderr).toContain("version=unknown");
expect(result.stderr).toContain("selected binary failed basic --version/--help sanity checks");
});
it("parses provider choices from the --provider flag help format", () => {
const result = runWrapper(
"Usage: crabbox run [options]\n --provider hetzner|aws|local-container|blacksmith-testbox|cloudflare\n",