mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-10 03:34:00 +00:00
* feat(scripts): serialize pr prepare gates and add remote testbox test gate Concurrent scripts/pr gate runs across .worktrees queued on the shared heavy-check lock mid-test: the queued run's children hit the 10-minute lock timeout while its unlocked build stage piled CPU load onto the holder's vitest shards, which then stalled past the 120s no-output watchdog and were SIGTERMed with zero real test failures (observed landing PRs #99935/#100026 on a loaded maintainer Mac). - scripts/pr-gates-lock.mjs holds the shared heavy-check lock for the whole local gate block; gate stages inherit the existing *_LOCK_HELD child contract, so concurrent gate runs now queue as whole units before their first command. - OPENCLAW_PR_GATES_REMOTE=testbox runs the full-suite pnpm test gate on a Blacksmith Testbox via scripts/crabbox-wrapper.mjs (same delegation as check:changed). The tbx_ lease id and Actions run URL from the crabbox --timing-json report are recorded in .local/gates.env (REMOTE_GATES_*) and .local/prep.md. Local remains the default; pnpm build/check stay local. Formatting verified with the primary checkout's oxfmt (hook bypassed: linked worktree has no hydrated node_modules). * fix(scripts): refresh the gate stamp when lease-retry gates rerun for a rebased head The lease-retry path reran build/check/test for the rebased prep head but left .local/gates.env describing the pre-push head, so prep.md/prep.env attributed stale evidence (including the new remote testbox lease id) to the pushed commit. Extract write_gates_env_stamp as the single stamp writer, rewrite the stamp from the retry path for all modes, and re-source gates.env in prepare_push after the push settles. Found by autoreview (codex/gpt-5.5); formatting verified with the primary checkout's oxfmt (hook bypassed: linked worktree has no node_modules). * fix(scripts): harden remote PR gate evidence * fix(scripts): serialize complete gate setup * fix(scripts): clear stale docs gate proof
76 lines
2.3 KiB
JavaScript
76 lines
2.3 KiB
JavaScript
// Holds the shared local heavy-check lock for a whole scripts/pr gate block so
|
|
// concurrent gate runs across .worktrees serialize before their first command
|
|
// instead of dying mid-test on child lock timeouts or no-output watchdog kills.
|
|
import fs from "node:fs";
|
|
import {
|
|
acquireLocalHeavyCheckLockSync,
|
|
resolveLocalHeavyCheckEnv,
|
|
} from "./lib/local-heavy-check-runtime.mjs";
|
|
|
|
// A queued gate block legitimately waits out another full gate run; the
|
|
// 10-minute per-command default in the lock runtime is far too short for that.
|
|
const DEFAULT_GATE_LOCK_TIMEOUT_MS = 2 * 60 * 60 * 1000;
|
|
const PARENT_WATCH_INTERVAL_MS = 2_000;
|
|
|
|
function parseArgs(argv) {
|
|
const args = { statusFile: "" };
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
if (argv[index] === "--status-file") {
|
|
args.statusFile = argv[index + 1] ?? "";
|
|
index += 1;
|
|
continue;
|
|
}
|
|
throw new Error(`Unknown option: ${argv[index]}`);
|
|
}
|
|
if (!args.statusFile) {
|
|
throw new Error("Usage: node scripts/pr-gates-lock.mjs --status-file <path>");
|
|
}
|
|
return args;
|
|
}
|
|
|
|
function main() {
|
|
const { statusFile } = parseArgs(process.argv.slice(2));
|
|
const baseEnv = resolveLocalHeavyCheckEnv(process.env);
|
|
const env = baseEnv.OPENCLAW_HEAVY_CHECK_LOCK_TIMEOUT_MS
|
|
? baseEnv
|
|
: { ...baseEnv, OPENCLAW_HEAVY_CHECK_LOCK_TIMEOUT_MS: String(DEFAULT_GATE_LOCK_TIMEOUT_MS) };
|
|
|
|
const parentPid = process.ppid;
|
|
const release = acquireLocalHeavyCheckLockSync({
|
|
cwd: process.cwd(),
|
|
env,
|
|
toolName: "pr-gates",
|
|
});
|
|
let released = false;
|
|
const releaseOnce = () => {
|
|
if (released) {
|
|
return;
|
|
}
|
|
released = true;
|
|
release();
|
|
};
|
|
|
|
process.on("exit", releaseOnce);
|
|
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
process.on(signal, () => {
|
|
releaseOnce();
|
|
process.exit(0);
|
|
});
|
|
}
|
|
|
|
fs.writeFileSync(statusFile, "acquired\n");
|
|
|
|
// The owner-pid reclaim in the lock runtime already covers a SIGKILLed
|
|
// holder; this watch releases promptly when the gate shell dies mid-block.
|
|
// ppid 1 also counts as dead: orphans reparent to init/launchd, and a
|
|
// helper that started orphaned has no gate block to hold the lock for.
|
|
setInterval(() => {
|
|
if (process.ppid !== parentPid || process.ppid === 1) {
|
|
releaseOnce();
|
|
process.exit(0);
|
|
}
|
|
}, PARENT_WATCH_INTERVAL_MS);
|
|
}
|
|
|
|
main();
|