From 0d9f3604e86ee8ae4a877a1d3fdf446dfdf14f92 Mon Sep 17 00:00:00 2001 From: Jesse Merhi <79823012+jesse-merhi@users.noreply.github.com> Date: Mon, 17 Aug 2026 16:33:28 +1000 Subject: [PATCH] fix(scripts): set the tsgo watchdog default from measured lane duration ClawSweeper on c7a699ee829 reversed its earlier guidance: the opt-in default adopted last iteration "deliberately preserves the indefinite tsgo hang that this PR is meant to fix". Its objection was never that a default existed, only that 45 minutes was unmeasured. Measured instead of guessed: hosted tsgo lanes (check-test-types, and its core stripes) complete in 1-2 minutes across recent successful main runs, against CI job caps of 15-20 minutes. 30 minutes is 15-30x the observed duration, leaves room for a far slower local host, and still bounds the 90-minute and multi-hour wedges that motivated this PR. OPENCLAW_TSGO_TIMEOUT_MS remains the documented override for hosts that need longer. Co-Authored-By: Claude Opus 5 (1M context) --- docs/help/testing.md | 11 ++++++----- scripts/run-tsgo.mts | 18 ++++++++++-------- test/scripts/run-tsgo.test.ts | 2 +- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/docs/help/testing.md b/docs/help/testing.md index 55a7989088fc..1fb985411ffb 100644 --- a/docs/help/testing.md +++ b/docs/help/testing.md @@ -694,11 +694,12 @@ Native dependency policy: after 5 minutes with no stdout or stderr output. Set `OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=0` to disable the watchdog for an intentionally silent investigation. - - `scripts/run-tsgo.mjs` waits indefinitely by default. Set - `OPENCLAW_TSGO_TIMEOUT_MS` to bound a run on hosts where a wedged - compiler would otherwise block its caller forever; on expiry the whole - tsgo process tree is killed and the run fails. Values above Node's - timer ceiling saturate instead of collapsing to a 1ms deadline. + - `scripts/run-tsgo.mjs` terminates a tsgo run after 30 minutes, so a + wedged compiler fails loudly instead of blocking its caller forever. + Hosted tsgo lanes finish in 1-2 minutes; raise + `OPENCLAW_TSGO_TIMEOUT_MS` on a slower host. On expiry the whole tsgo + process tree is killed and the run fails. Values above Node's timer + ceiling saturate instead of collapsing to a 1ms deadline. diff --git a/scripts/run-tsgo.mts b/scripts/run-tsgo.mts index f0a090cf37d1..891ed26c435f 100644 --- a/scripts/run-tsgo.mts +++ b/scripts/run-tsgo.mts @@ -16,6 +16,12 @@ import { shouldSkipSparseTsgoGuardError, } from "./lib/tsgo-sparse-guard.mts"; +/** + * Hosted tsgo lanes finish in 1-2 minutes and their CI jobs cap at 15-20, so 30 + * leaves headroom for a far slower local host while still bounding a wedge that + * would otherwise never report. Raise OPENCLAW_TSGO_TIMEOUT_MS for slower hosts. + */ +const DEFAULT_TSGO_TIMEOUT_MS = 30 * 60 * 1000; /** Node's timer ceiling: a longer delay silently becomes 1ms, so a raised override must saturate. */ const MAX_TSGO_TIMEOUT_MS = 2_147_483_647; @@ -49,14 +55,10 @@ async function main(): Promise { } ensureRepoToolNodeModulesLink(tsgoPath); - // Opt-in deadline: no supported duration contract covers every host and project, - // so an unset value keeps the pre-existing unbounded wait rather than guessing one. - const timeoutMs = env.OPENCLAW_TSGO_TIMEOUT_MS?.trim() - ? Math.min( - readPositiveEnvInt("OPENCLAW_TSGO_TIMEOUT_MS", env, MAX_TSGO_TIMEOUT_MS), - MAX_TSGO_TIMEOUT_MS, - ) - : undefined; + const timeoutMs = Math.min( + readPositiveEnvInt("OPENCLAW_TSGO_TIMEOUT_MS", env, DEFAULT_TSGO_TIMEOUT_MS), + MAX_TSGO_TIMEOUT_MS, + ); try { // Managed run owns the whole tsgo process tree: on timeout it SIGKILLs the // process group, because a wedged checker ignores SIGTERM and would otherwise diff --git a/test/scripts/run-tsgo.test.ts b/test/scripts/run-tsgo.test.ts index b325a0367b80..60bc19647ca1 100644 --- a/test/scripts/run-tsgo.test.ts +++ b/test/scripts/run-tsgo.test.ts @@ -277,7 +277,7 @@ describe.skipIf(process.platform === "win32")("run-tsgo watchdog", () => { expect(result.stderr.trim().split("\n").at(-1)).toBe("[tsgo] FAILED (exit 1)"); }, 30_000); - it("arms no watchdog until an operator opts in", () => { + it("leaves a healthy run alone under the default deadline", () => { const cwd = createTempDir("openclaw-run-tsgo-watchdog-"); writeFakeTsgo(cwd, "#!/bin/sh\nsleep 2\nexit 0\n");