chore(test): remove the contention retry (0 fires since 07-29) and shrink the subprocess-stub project to its 3 real spawners (#1781 A4) - #1827
Conversation
…ub project (#1781 A4) The enumerated single-retry policy (#1419) has fired zero times since it landed on 2026-07-29: 0 of 234 sampled Coverage-job lane envelopes (2026-08-11 to 2026-08-18) have retryCount > 0, and none of 17 recent failed runs was retried (5 refused "outside the enumerated retry list", 4 refused "unhandled error"). All three trackers its entries pointed at (#1098, #1414, #1419) are closed. It cost ~1,454 LOC, a per-run secret marker threaded through a setup file on every Vitest project, and a standing obligation for every future gate reporter to call the blocker bus. Delete the scripts, tests and fixtures, the check:contention-retry script and gate, the envelope artifact upload, and the runner-timeout setup file; test:coverage:ci is a plain `vitest run --coverage` again. lane-envelope.ts stays: the mutation, fuzz and concurrency-torture lanes build their envelopes from it. run-blocker-bus.ts goes: its only consumer was the retry's failure sink, and its only publisher already fails the run by setting process.exitCode. Keep the subprocess-stub project for the three files that really spawn (client-metro, fuzz harness, fuzz corpus-replay) and drop the three that run in 31/212/277ms in CI, which cannot contend for anything. The list is now a plain array in vitest.config.ts with the reason at each entry. Membership and the project's kill criterion live in #1823. Because test:coverage:ci is a bare vitest run, the gate manifest reads its projects directly, so OPAQUE_RUNNERS no longer needs it and an unrun Vitest project becomes unrepresentable rather than detected; the audit test now constructs that state by project-scoping the script.
|
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
thymikee
left a comment
There was a problem hiding this comment.
Adversarial pass over #1827 (checked out origin/chore/1781-a4-contention-retry @ 57b93d6 in a throwaway worktree, pnpm install, then ran the actual gates rather than trusting the PR-body claims).
Verified against the diff (no leftover references found)
Grepped the whole tree for contention-retry, contentionRetry, CONTENTION_RETRY, runner-timeout/RUNNER_TIMEOUT/runnerTimedOut/takeRunnerTimeoutToken, run-blocker-bus/recordRunBlocker/drainRunBlockers, serializedStub, timeout-provenance, and #1419/#1430 across .ts/.md/.yml/.json. Only hits:
docs/agents/testing.md:506— the intended "Removed 2026-08-18 (#1781 A4)" historical note.src/platforms/apple/core/__tests__/screenshot.test.ts— amkdtempForTest('agent-device-ios-runner-timeout-')fixture-name string, unrelated (predates this PR, not touched by it). False positive.#1430hits are all the keptlane-envelope.tsconsumers (mutation/fuzz/concurrency-torture), consistent with the PR's claim that lane-envelope itself is untouched.
No dangling doc anchors, no orphaned imports, nothing in .github/, scripts/gate/declarations.ts, scripts/check-affected/{checks,model}.ts, .fallowrc.json, package.json scripts, or either vitest config still points at a deleted symbol/file.
Ran the gates myself (all green, matching the PR-body numbers exactly)
pnpm check:gate-manifest→46 checks wired across 33 lanes— matches the body verbatim.pnpm check:gate-manifest:test→ 37/37 pass — matches.pnpm check:affected:test→ 36/36 pass — matches.pnpm check:fallow→No issues in 25 changed files.node --test scripts/gate/audit-coverage.test.ts→ 6/6 pass, including the newprojectScopedcase that makes "an unrun Vitest project" representable now thatOPAQUE_RUNNERSis empty (scripts/gate/declarations.ts:6, consumed byscripts/gate/model.ts:199).vitest run --project unit-core src/platforms/android/__tests__/app-deployment.test.ts src/__tests__/platform-runtime-runtime-hints.test.ts src/platforms/apple/core/__tests__/apps.test.ts→ 3 files / 72 tests pass inunit-core. Confirms item (3):vitest.config.ts'sunit-core.exclude: [...SUBPROCESS_STUB_TESTS]is derived dynamically from the same shrunk 3-entry array thesubprocess-stubproject'sincludeuses, so the 3 dropped files fall straight intounit-corevia its existingsrc/**/*.test.tsglob rather than being silently unlisted anywhere.oxlinton every touched non-deleted file: clean.
test:coverage:ci argv/reporter equivalence (item 2) — confirmed correct
Old contention-retry-run.ts's firstRunArgs was called with modes.projects derived from an unset --project CLI flag (package.json never passed one to the wrapper), so projectArgs([]) = [], and the first run was literally vitest exec vitest run --coverage with reporters resolved from reporters(env) where the only difference from bare config was appending contentionRetryReporter() (now correctly deleted along with the mechanism it fed). New "test:coverage:ci": "vitest run --coverage" is byte-for-byte the same argv the old wrapper produced for its first run, and vitest.config.ts's top-level reporters: ['default', slowTestGateReporter()] is unchanged — so the claim "reporters come from vitest.config.ts, which no lane overrides" holds.
Downstream: "Enforce changed-line coverage gate" in ci.yml still runs unconditionally after unit-ci and still reuses the lcov the Coverage step wrote — coverage.reporter in vitest.config.ts (untouched by this diff) still includes 'lcov'. The deleted "Upload contention-retry envelope" artifact step had zero other consumers in the repo (grepped), consistent with the mechanism it fed being gone.
Issues #1823 / #1824
Both open and match the PR body's claims precisely — #1823 lists the same 3 kept members + measured durations + the same 20-consecutive-CI-runs kill criterion; #1824 states the same "Worker exited unexpectedly" signature, run IDs, and "the retry refused this class by design and is now gone" framing. No discrepancy.
Minor / non-blocking
package.json:test:coverage("vitest run --coverage") andtest:coverage:ci(also now"vitest run --coverage") are byte-identical scripts. Harmless — one is the CI gate entrypoint (scripts/gate/declarations.tsno longer needs to special-case it) and one is the local ad-hoc command — but a future reader diffing the two will wonder why there are two names for one command. Not worth blocking on.- I chased a hunch that shrinking
SUBPROCESS_STUB_TESTS(whichvitest.mutation.config.ts:6,38also consumes for itsexclude) would let PATH-mutating files reach Stryker's thread-pool mutation runner unprotected. Empirically two of the three dropped files (app-deployment.test.ts, realprocess.env.PATH =mutation with try/finally restore) are already reachable viavitest relatedfrom the currentstryker.config.jsonmutatelist — so this isn't hypothetical, it's live today. Butscripts/mutation/test-scope.ts's own stated thread-pool exclusion criterion is only chdir/worker_threadsusage, never generic env mutation, and the files restorePATHsynchronously infinallywith notest.concurrentin the file (globalmaxConcurrencydoesn't even apply since there's no concurrent test), so there's no realistic race. Checked, not a bug — noting only so nobody re-treads this.
Verdict
Everything the PR body claims reproduces exactly under a real re-run of the gates it cites, and an exhaustive grep across the whole tree turns up no leftover reference to any deleted symbol, file, or gate. The evidence for deleting the retry (0/234 fires, all three trackers closed) and for shrinking the stub list (measured per-file durations) is solid and independently checked here. This is about as clean a deletion PR as they come.
VERDICT: approve
One-line summary: exhaustive grep + re-running every gate the PR cites (check:gate-manifest, check:gate-manifest:test, check:affected:test, check:fallow, the 3 moved-back unit files, both linked issues) all confirm the PR body's claims with zero leftover references found.
|
Reviewed exact head 57b93d6: clean. The contention retry is removed end-to-end without leaving stale workflow/gate/docs ownership; the bare coverage command preserves the wrapper’s first-run invocation. Subprocess-stub membership now has one source of truth and retains only the three genuine subprocess/watchdog tests, with gate and linked-issue evidence covering the decision. No device evidence is needed for this tooling-only change. Code review is ready-for-human; iOS Smoke is still in progress and the PR remains draft. |
#1781 item A4. Two changes, one theme: keep the part of the contention story that still pays, delete the part that never fired.
(a) The contention single-retry policy is removed
It has fired 0 times since it landed on 2026-07-29 (#1448):
retryCount > 0. 20 of those runs failed; none was retried.REVIEW_BY = 2026-10-31), so the expiry gate would not have surfaced this for another 74 days.What it cost: ~1,454 LOC deleted —
scripts/lib/contention-retry{,-args,-blockers,-lane,-reporter,-run}.ts(747),contention-retry-policy.test.ts(531),runner-timeout-meta.ts+scripts/vitest-runner-timeout-setup.ts+run-blocker-bus.ts(71),test/contention-retry-fixtures/*(75), config (~30) — plus a per-run secret marker threaded through a setup file on every Vitest project, and a standing obligation for every future gate reporter to callrecordRunBlocker.Also removed: the
check:contention-retrypackage script and its place incheck:unit, thecontention-retrygate (catalog + CI step), thecontention-retry-envelopeartifact upload, and thevitest-runner-timeout-setup.tssetup-file wiring.test:coverage:ciis a plainvitest run --coverageagain — the exact argv the wrapper passed through for a coverage run (reporters come fromvitest.config.ts, which no lane overrides).Kept, with a note:
scripts/lib/lane-envelope.ts(#1430) is not retry-specific — the mutation lane (scripts/mutation/run.ts), the fuzz lane (scripts/fuzz/envelope.ts) and the concurrency-torture lane all build their envelopes from it. Only the retry's consumer (contention-retry-lane.ts) and the retry's artifact-upload step are gone.run-blocker-bus.tsis deleted: its only consumer was the retry's failure sink, and its only publisher (the slow-test ratchet) already fails the run by settingprocess.exitCode = 1.One follow-on:
OPAQUE_RUNNERSloses itstest:coverage:cientry, because a barevitest run --coverageis something the gate-manifest loader reads directly. That makes "a Vitest project no registered check runs" unrepresentable rather than merely detected, soscripts/gate/audit-coverage.test.tsnow constructs that state by project-scoping the coverage script in its model mutation.(b) The
subprocess-stubproject stays, shrunk to the 3 files that really spawnThe list had 10 entries; 6 were serialized. Measured in CI (run 32127451031) and locally:
src/__tests__/client-metro.test.tsscripts/fuzz/harness.test.tsscripts/fuzz/corpus-replay.test.tssrc/platforms/android/__tests__/app-deployment.test.tssrc/__tests__/platform-runtime-runtime-hints.test.tssrc/platforms/apple/core/__tests__/apps.test.tsvi.mocks the exec seamAll three now run in
unit-coreand pass there (verified locally: 3 files / 72 tests, 7.8s, slowest case 1.57s — inside the 2.5s slow-test budget). The 4 retry-only entries (request-router-open.test.ts,runner-client.test.ts,runner-xctestrun.test.ts— whose stated reason was false: itvi.mocksnode:child_processand nothing spawns — andhelp-conformance-bench.test.ts) simply cease to be listed anywhere. The optional in-process conversions for them are deliberately not done here.SUBPROCESS_STUB_TESTSis now a plain 3-entry array invitest.config.tswith one line per entry saying why it spawns;fileParallelism: false, isolate: true, maxWorkers: 1are unchanged.Lane accounting
subprocess-stubproject — retainedandroid/index.test.tsaggregation per the config comment; the 3 members measured above genuinely spawn per case. (process.envisolation, the other stated motive, already comes frompool: forks+isolate: true.)unit-coreis the 296s critical path); ~25-37s serialized chain locally.Contention retry — removed. Catches: runner-proven timeouts in enumerated files only / Evidence: 0 fires in ~3 weeks, refused every observed failure class / Cost: ~1.45k LOC + a per-run secret marker + a bus every future gate reporter must call / Killed.
Issues filed
subprocess-stubvitest project: members and kill criterion (the live tracker the config comment points at; all three old trackers were closed).Test plan
pnpm test:unit— 891 files / 6,847 tests pass, including the three files moved back intounit-core.pnpm test:coverage:ci— run locally; writescoverage/lcov.info.pnpm typecheck,pnpm lint,pnpm format:check— green.pnpm check:gate-manifest(46 checks across 33 lanes) andpnpm check:gate-manifest:test(37 tests) — green.pnpm check:affected:test(36 tests),pnpm check:layering(183 tests + guard),pnpm check:fallow— green.pnpm check:production-exportsreports the same 17 pre-existing exports as cleanorigin/main(verified by stashing).