fix(scripts): clamp boundary check timers

This commit is contained in:
Vincent Koc
2026-06-22 02:54:24 +02:00
parent fe7b78b05f
commit 08442c4b38
2 changed files with 30 additions and 5 deletions
@@ -37,6 +37,7 @@ const FAILURE_OUTPUT_TAIL_LINES = 40;
const STEP_OUTPUT_MAX_CHARS = 256 * 1024;
const STEP_PROCESS_GROUP_EXIT_POLL_MS = 25;
const STEP_POST_FORCE_KILL_WAIT_MS = 1_000;
const MAX_TIMER_TIMEOUT_MS = 2_147_000_000;
const SLOW_COMPILE_SUMMARY_LIMIT = 10;
const COMPILE_INPUT_EXTENSIONS = new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".json"]);
const ROOTDIR_BOUNDARY_CANARY_IMPORT_PATH =
@@ -337,13 +338,22 @@ function writeStampFile(filePath) {
writeFileSync(filePath, `${new Date().toISOString()}\n`, "utf8");
}
function resolveStepTimerTimeoutMs(valueMs) {
const value = Number(valueMs);
if (!Number.isFinite(value)) {
return MAX_TIMER_TIMEOUT_MS;
}
return Math.min(Math.max(Math.floor(value), 1), MAX_TIMER_TIMEOUT_MS);
}
function runNodeStep(label, args, timeoutMs) {
const resolvedTimeoutMs = resolveStepTimerTimeoutMs(timeoutMs);
const startedAt = Date.now();
const result = spawnSync(process.execPath, args, {
cwd: repoRoot,
encoding: "utf8",
maxBuffer: 16 * 1024 * 1024,
timeout: timeoutMs,
timeout: resolvedTimeoutMs,
});
if (result.status === 0 && !result.error) {
@@ -352,7 +362,7 @@ function runNodeStep(label, args, timeoutMs) {
const timeoutSuffix =
result.error?.name === "Error" && result.error.message.includes("ETIMEDOUT")
? `${label} timed out after ${timeoutMs}ms`
? `${label} timed out after ${resolvedTimeoutMs}ms`
: "";
const errorSuffix = result.error ? result.error.message : "";
const note = [timeoutSuffix, errorSuffix].filter(Boolean).join("\n");
@@ -391,6 +401,7 @@ function abortSiblingSteps(abortController) {
* Runs one node-based boundary check step with timeout and output capture.
*/
export function runNodeStepAsync(label, args, timeoutMs, params = {}) {
const resolvedTimeoutMs = resolveStepTimerTimeoutMs(timeoutMs);
const abortController = params.abortController;
const killProcess = params.killProcess ?? process.kill.bind(process);
const onFailure = params.onFailure;
@@ -499,7 +510,7 @@ export function runNodeStepAsync(label, args, timeoutMs, params = {}) {
stderr: stderrText,
kind: "timeout",
elapsedMs: Date.now() - startedAt,
note: `${label} timed out after ${timeoutMs}ms`,
note: `${label} timed out after ${resolvedTimeoutMs}ms`,
}),
),
label,
@@ -508,14 +519,14 @@ export function runNodeStepAsync(label, args, timeoutMs, params = {}) {
stderr: stderrText,
kind: "timeout",
elapsedMs: Date.now() - startedAt,
note: `${label} timed out after ${timeoutMs}ms`,
note: `${label} timed out after ${resolvedTimeoutMs}ms`,
},
);
onFailure?.(error);
abortSiblingSteps(abortController);
rejectPromise(toLintErrorObject(error, "Step timed out"));
})();
}, timeoutMs);
}, resolvedTimeoutMs);
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
@@ -5,6 +5,7 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion";
import { afterEach, describe, expect, it } from "vitest";
import {
acquireBoundaryCheckLock,
@@ -432,6 +433,19 @@ describe("check-extension-package-tsc-boundary", () => {
expect(elapsedMs).toBeGreaterThanOrEqual(0);
}, 30_000);
it("clamps oversized async node step timers before scheduling", async () => {
await expect(
runNodeStepAsync(
"slow-success",
["--eval", "setTimeout(() => process.exit(0), 25);"],
MAX_TIMER_TIMEOUT_MS + 1,
),
).resolves.toMatchObject({
stderr: "",
stdout: "",
});
});
it("keeps async node step failure output bounded", async () => {
const child = new EventEmitter() as EventEmitter & {
kill: (signal?: NodeJS.Signals | number) => boolean;