feat(#4422): block merging into next/main while the base branch's Tests run is red (#4428)

* feat(#4422): block merging into next/main while the base branch's Tests run is red

Adds a next-health job to test.yml that checks the base branch's own last
push-triggered Tests run via the GitHub API and fails the existing "Required
tests" required check when it's red, with a maintainer-applied "fix-next"
label as the explicit escape hatch for the fix-forward PR itself. No
branch-protection config change needed — it rides the already-required
check. The job is deliberately not gated behind preflight, same reasoning
as the changes job: a compute-free API read has nothing to save by waiting.

Documents the fix-next label in CONTRIBUTING.md and adds a property test
locking the CLEAN/RED/INDETERMINATE classification's iff-relationship.

This closes the second half of the 2026-09-06 RCA: three unrelated PRs
merged on top of an already-broken next before anyone noticed it was red.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: close two zero-margin CI timing gaps found while verifying #4422

Discovered while watching this branch's own CI, root-caused via /diagnose
rather than dismissed as Windows flakiness:

1. tests/gsd-check-update-worker-atomic-cache.test.cjs's outer timeout
   (15000ms) exactly matched the inner npm-view timeout the worker wraps
   (NPM_VIEW_TIMEOUT_MS, gsd-core/bin/check-latest-version.cjs). A slow
   registry response raced two SIGKILLs at the same instant, killing the
   worker before it could catch its own timeout and degrade gracefully.
   Windows's shell-wrapped npm subprocess made the race lose more often
   there, but the zero margin was platform-agnostic. Fixed by giving the
   test real headroom (+10s) beyond the named constant it wraps, plus an
   invariant test so the two values can't silently collide again.

2. scripts/run-tests.cjs's per-chunk weight budget (MAX_FILES_PER_CHUNK)
   let a Windows full-matrix chunk that was well under budget by the
   Linux/macOS-calibrated weight table (~32/60 units) still exceed the
   600s wall-clock backstop — codex-config.test.cjs's genuinely-measured
   weight (17.87) doesn't transfer 1:1 to Windows's slower install/
   subprocess overhead. Windows now gets its own lower cap (40 vs 60).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-09-06 17:29:54 -04:00
committed by GitHub
parent 38e4ce5f62
commit 1c0acb2359
7 changed files with 969 additions and 5 deletions

View File

@@ -1273,7 +1273,21 @@ function main() {
// node process (also relieving per-process memory pressure from 170+ files at once).
// Lowered from 90 to 60 after #1575 — macOS Node 22 shard 2/3 chunk 2 (~80 files
// including state.test.cjs, perf-*, worktree-cleanup) exceeded 600s with 90.
const MAX_FILES_PER_CHUNK = positiveNumberEnv(process.env.RUN_TESTS_MAX_FILES_PER_CHUNK, 60);
//
// 2026-09-06 (PR #4428 CI): a Windows full-matrix chunk (chunk 3/6, ~32/60
// weight-budget units, dominated by codex-config.test.cjs at a genuinely
// MEASURED weight of 17.87 — not a stale-table miss) still exceeded the
// 600s per-chunk backstop. The weight table's calibration does not
// transfer 1:1 to the Windows runner for install/subprocess-heavy work —
// it needs a smaller budget than Linux/macOS to stay inside the same
// wall-clock ceiling. Windows gets its own, lower cap (~33% reduction,
// proportionate to the >30% single-file share codex-config.test.cjs alone
// consumed of that chunk's budget); other platforms are unaffected.
const DEFAULT_MAX_FILES_PER_CHUNK = process.platform === 'win32' ? 40 : 60;
const MAX_FILES_PER_CHUNK = positiveNumberEnv(
process.env.RUN_TESTS_MAX_FILES_PER_CHUNK,
DEFAULT_MAX_FILES_PER_CHUNK,
);
// #2088 established that file COUNT is a poor proxy for a chunk's wall-clock:
// install-heavy files (real installs) cost ~10x a unit file, and when several
// land in the SAME chunk it blows the 600s backstop while unit-only chunks