fix(ci): reduce Windows test concurrency 4→2 to prevent synckit worker exhaustion on Node 24 (#173)

Under Node 24 on Windows, running node --test with --test-concurrency=4
causes 4 concurrent gsd-tools subprocesses to each spawn a synckit
worker_threads worker for the SDK bridge. The 4 workers simultaneously
contend on SharedArrayBuffer + Atomics.wait under Windows Defender
scanning and NTFS latency, triggering OS-level resource exhaustion that
kills worker processes with empty stderr before any output is flushed.

The symptom: intermittent exit 1 with 0 test failures, varying affected
test files per run, all sharing the pattern of invoking gsd-tools as a
subprocess. Empty stderr distinguishes OS crash from gsd-tools app error
(the error() path writes to stderr before exiting).

Fix: platform-aware concurrency default — 2 on win32, 4 on Linux/macOS.
The existing TEST_CONCURRENCY env-var override is preserved. Also adds
a [stderr: (empty) exit:N] diagnostic note in helpers.cjs runGsdTools
catch block so future empty-stderr crashes are visible in CI logs.

Fixes gsd-build/get-shit-done#3869

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-23 23:10:06 -04:00
committed by GitHub
parent 6177e3a5f5
commit 3169d5cda6
2 changed files with 24 additions and 2 deletions

View File

@@ -62,10 +62,18 @@ function runGsdTools(args, cwd = process.cwd(), env = {}) {
}
return { success: true, output: result.trim(), exitCode: 0 };
} catch (err) {
const stderrRaw = err.stderr?.toString().trim() || '';
// Prefer actual stderr content; fall back to err.message (which contains
// the command invocation). If stderr is empty, append a note so CI logs
// show "stderr: (empty)" rather than silently losing the fact that the
// child process produced no error output — empty stderr with a non-zero
// exit code is a signal of OS-level crash (OOM kill, worker thread fatal
// error) rather than a gsd-tools application error.
const error = stderrRaw || `${err.message} [stderr: (empty) exit:${err.status ?? 1}]`;
return {
success: false,
output: err.stdout?.toString().trim() || '',
error: err.stderr?.toString().trim() || err.message,
error,
exitCode: err.status ?? 1,
};
}