ci(tests): widen the Windows shard watchdog and keep exit diagnostics (#2449)

The busiest Windows platform shard reached 14m57s against the 15 minute
watchdog on the rc.19 green run and has timed out once since. CI now
sets GITNEXUS_CROSS_PLATFORM_TIMEOUT_MINUTES=20 (the job timeout stays
25), the stale comfortably-under comment reflects reality, and the
runner always logs status, signal, spawn code and elapsed time so the
next status-null death is diagnosable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Gergo Magyar 2026-07-16 11:03:43 +00:00 committed by Gergő Magyar
parent 42de243e9a
commit 573a777ef5
2 changed files with 28 additions and 7 deletions

View file

@ -184,8 +184,10 @@ jobs:
# spawns and Windows is ~5x slower than macOS at those, so the unsharded
# run crept past the 15-min watchdog in run-cross-platform.ts. vitest
# shards by file COUNT, not runtime, so the heaviest spawn suites can
# cluster on one shard; 3 shards keep even the busiest Windows shard
# comfortably under the watchdog (macOS had margin either way).
# cluster on one shard. The busiest Windows shard has grown to the old
# 15-minute watchdog (14m57s on the v1.6.10-rc.19 green run, one
# observed timeout since — #2449), so the job env below raises the
# per-shard watchdog to 20 minutes, still bounded by timeout-minutes.
# Shard indices come from the shard-plan job (single source of truth):
# its TOTAL drives this list and the /N in the job name + --shard arg.
shard: ${{ fromJSON(needs.shard-plan.outputs.shards) }}
@ -204,6 +206,10 @@ jobs:
env:
GITNEXUS_REQUIRE_FTS: '1'
GITNEXUS_E2E_CLI: dist
# #2449: hosted Windows runners intermittently push the busiest shard past
# the default 15-minute watchdog. 20 minutes restores real headroom while
# the 25-minute job timeout above still bounds a genuine hang.
GITNEXUS_CROSS_PLATFORM_TIMEOUT_MINUTES: '20'
steps:
# persist-credentials: false — runs tests only, never pushes (zizmor
# credential-persistence / artipacked audit).

View file

@ -48,10 +48,11 @@ try {
// Per-shard watchdog, default 15 min. Sharding splits the file list by COUNT, not
// runtime, so the heaviest spawn suites can cluster on one shard — what this
// bounds is the *busiest* shard, not an even 1/n of wall-clock. With 3 shards
// even that shard clears the watchdog, where the whole unsharded Windows run
// used to trip it. Allow CI/manual runs to add headroom without editing the
// script again.
// bounds is the *busiest* shard, not an even 1/n of wall-clock. The busiest
// Windows shard has grown to the default (14m57s on the v1.6.10-rc.19 green
// run, one observed timeout since — #2449), so CI raises the budget to 20
// minutes via GITNEXUS_CROSS_PLATFORM_TIMEOUT_MINUTES; the default stays 15
// for local runs.
const DEFAULT_TIMEOUT_MIN = 15;
const timeoutMinutes = Number.parseInt(
process.env.GITNEXUS_CROSS_PLATFORM_TIMEOUT_MINUTES ?? String(DEFAULT_TIMEOUT_MIN),
@ -67,6 +68,7 @@ console.log(
`${shardArg ? ` (${shardArg.replace('--shard=', 'shard ')})` : ''}...\n`,
);
const startedAt = Date.now();
try {
execFileSync('npx', ['vitest', 'run', ...ALL_CROSS_PLATFORM, ...(shardArg ? [shardArg] : [])], {
cwd: ROOT,
@ -76,9 +78,22 @@ try {
});
} catch (err) {
// execFileSync sets `killed`/`signal` when the watchdog above kills vitest.
const e = err as { killed?: boolean; signal?: NodeJS.Signals | null };
const e = err as {
killed?: boolean;
signal?: NodeJS.Signals | null;
status?: number | null;
code?: string;
};
if (e.killed || e.signal) {
console.error(`vitest timed out after ${Math.round(timeoutMs / 60_000)} minutes`);
}
// #2449: Windows shards have died with a bare `status: null`, empty stderr
// and nothing to triage from. Always leave the child's exit facts behind.
const elapsedSec = Math.round((Date.now() - startedAt) / 1000);
console.error(
`vitest exited abnormally: status=${e.status ?? 'null'} signal=${e.signal ?? 'none'} ` +
`killed=${e.killed === true} spawnCode=${e.code ?? 'none'} elapsed=${elapsedSec}s ` +
`budget=${Math.round(timeoutMs / 60_000)}min`,
);
process.exit(1);
}