test(ci): single-retry policy for enumerated contention-flaky files (timeouts only) - #1448
test(ci): single-retry policy for enumerated contention-flaky files (timeouts only)#1448devin-ai-integration[bot] wants to merge 11 commits into
Conversation
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Size Report
Startup median (7 runs, lower is better):
Top changed chunks: no changes in the largest emitted chunks. |
|
|
Not ready — the contention-retry harness still has four gate-integrity problems:
Required validation: adversarial coverage for a timeout plus each non-test failure class; assertion messages containing timeout words; configured slow-test reporter preservation; and two timed-out tests in one file producing one retry. |
|
All four fixed in aa76fca. The through-line: the lane now only retries when the run's entire failure surface is a set of runner-raised timeouts in listed files. 1 — combined failures (
2 — assertion text ( 3 — slow-test gate. You're right, 4 — overcounting. Summary rows and
|
|
Re-reviewed
Required validation: exact-template plain |
|
Both closed in 06bd25b. 1 — eligibility now keys on runner metadata, not text (
2 — gate verdicts ride a structured channel (
Regressions held: a genuine injected timeout in a listed file still retries and passes with its summary row; module/unhandled/coverage blockers, reporter preservation, and per-file telemetry are all still green (24-case gate, |
06bd25b to
35ac931
Compare
|
Re-reviewed latest head Still not ready — P1 timeout provenance remains forgeable. Required validation: an actual Vitest fixture that blocks beyond timeout then throws the exact template must fail on the first run with zero retry; otherwise derive eligibility from genuinely runner-owned structured provenance or fail closed. |
|
Fixed in 65693d2 — eligibility no longer infers anything. The runner marks its own timeouts.
beforeEach(({ signal, task }) => {
onTestFailed(() => {
if (signal.aborted && signal.reason instanceof Error && RUNNER_TIMEOUT_REASON.test(signal.reason.message)) {
task.meta[RUNNER_TIMEOUT_META] = true;
}
});
});
Your event-loop-blocked forgery is exactly why the check moved: I reproduced it first and it did pass the old duration rule. Under the new rule it doesn't, and the interesting part is why — blocking the loop past the budget denies the runner's timer its turn, so the signal is never aborted; the test's own throw is what gets reported, unmarked. Required validation, as a permanent gate.
It then asserts the plan: with any of those present the run does not retry. End-to-end in the real lane, an injected blocking forgery in a listed file exits 1 with Full |
|
P1 — The new timeout provenance remains forgeable because it is stored in test-writable |
Fixed in 2ef3024. You're right that Metadata mutation is now exercised for real in the child Vitest run (
Plus unit coverage that
|
2ef3024 to
8bbdcf3
Compare
|
[P1] Preserve coverage and project modes on the retry. In |
Fixed in 9a0b8b2. Both runs now build their argv from one place, firstRunArgs({ projects: ['unit-core'], coverage: true })
// ['--project','unit-core','--coverage']
retryRunArgs({ projects: ['unit-core'], coverage: true }, [file])
// ['--project','unit-core','--coverage',
// '--coverage.reportsDirectory=coverage/contention-retry',
// '--coverage.thresholds.statements=0','--coverage.thresholds.lines=0', file]so the retry reproduces the selected projects and the V8 instrumentation. Two deliberate differences, both narrow:
Regression coverage in format/lint/typecheck/fallow and the policy gate (25 cases) are green. |
9a0b8b2 to
96f174d
Compare
|
Re-reviewed exact head 05a3306. The prior retry-mode P1 is fixed: retries preserve selected projects and V8 coverage, isolate their coverage output, and the regression pins argv plus entrypoint wiring. The latest commit is tree-empty and only retriggers CI, so it does not address the previous iOS Smoke failure at keyboard dismiss with UNSUPPORTED_OPERATION. Current static and integration checks are green, but iOS Smoke and other jobs are still running. Code review is clean; readiness remains blocked on resolving or credibly classifying that owner-action/base CI failure. |
Classifying that failure, with evidence — all 30 checks on What failed on
So the failure is neither in the rebase result nor in this diff (which touches no interaction, keyboard, or runner code — only CI retry-lane scripts, Per AGENTS.md this is exactly the "reproduce on plain |
|
Correction: exact head |
…e verdicts through blockers
Cover direct task.meta mutation in the real child-Vitest fixture gate. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…age modes Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
05a3306 to
4c1286c
Compare
You're right, and my previous classification cited the wrong job — apologies. Here is the iOS lane specifically (workflow iOS Smoke history for this branch and its base
So the two failures are a contiguous window on one tree, and the same tree rebased forward passes the same scenario. The failing step is a live simulator behavior: the XCTest runner returned Current head is I did not "fix" anything in the iOS path, so if you'd rather have it tracked than classified as a |
|
Re-review clean at |
Summary
Closes #1419 (umbrella #1412 Track B).
Contention-flaky files were a prose trap in
docs/agents/testing.mdand a glob invitest.config.ts. This makes the set an enumerated constant of owned waivers and gives CI a narrow retry rule: one rerun, of the failed files only, and only when the failure is timeout-shaped.scripts/lib/contention-retry.tsis the one list —SUBPROCESS_STUB_TESTSis now derived from it, so the serialized Vitest project and the retry policy cannot drift:vitest list --project subprocess-stubis byte-identical toorigin/main, so the execution contract is unchanged.The decision, in
planContentionRetry: retry requires every failure to be timeout-shaped and in a listed file. One assertion failure — listed or not — fails the job on the first run, so a regression can never be papered over. The Coverage job runs the suite throughpnpm test:coverage:ci; every retried file is named in the job summary with its tracking issue and review date, and the run always writes the shared lane envelope (scripts/lib/lane-envelope.ts, #1430) carryingretryCount/retryOutcome, uploaded ascontention-retry-envelope.Two things worth flagging:
jsonreporter serializes a timeout's message as a bareSTACK_TRACE_ERROR, erasing exactly the distinction the policy is built on.contention-retry-reporter.tskeeps each failed test's real error message and nothing else. Classifying by message text is an owned exception to "typed signals over message sniffing" (there is no structured timeout signal on a reporter result); it errs towards "not a timeout", which fails the job.pnpm check:contention-retry(own CI step, and part ofpnpm check:unit) fails on a pastreviewBy, a missing file, a glob-looking entry, or areasonthat does not name a spawn/wait — so an entry is renewed or removed, never inherited. Expiry is checked before the suite runs, so an expired list cannot spend its retry.Acceptance, verified end-to-end
Injected into
src/daemon/__tests__/runtime-hints.test.ts(a listed file), run through the real entrypoint:runFilesnever invoked:No retry: a listed file failed for a non-timeout reason (assertion failures never retry).Retried 1 timeout-shaped file(s) once — outcome: **passed**plus the file, issue and review date, andretryCount: 1in the envelope.format:check,lint,typecheck,check:fallow,check:contention-retryand the fullpnpm test:coverage:ci(whole suite under coverage, thresholds met) are green.pnpm check:affected --base origin/main --runselects the full set and stops atbuild:tsdownfails locally withFailed to import module "unrun"— reproduced unchanged on a cleanorigin/mainworktree, so it is a local toolchain gap, not this diff. GitHub CI covers it.Link to Devin session: https://app.devin.ai/sessions/3dfe358943144f63968bb64ef05ea209
Requested by: @thymikee