You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
browser cycle (chromium) — the full-cycle Playwright run against a real server — fails intermittently at cycle/cycle.spec.ts:321:
Error: expect(locator).toContainText(expected) failed
Expected substring: "Saved"
Received string: "This has already moved on — reload to see where it is now."
The rendered string is refusalProse for INVALID_TRANSITION. That code cannot come from the save itself: AnnotationService.add refuses a closed batch with BatchNotInAnnotation, never InvalidTransition.
It comes from the auto-start effects. AnnotationPage's SaveState renders save.error and falls back to openingRefusal — the state that catches a refused POST /batches/{id}/start or POST /jobs/{id}/start fired on open (#300, and the StrictMode ref-guard added there). So the sequence is: the batch or the job was already started when the effect sent the transition, the kernel answered INVALID_TRANSITION, and the refusal landed in the same slot the save reports through — where the spec was waiting for "Saved".
Likely races:
the cycle spec enters the annotator through a tile, and the batch may already be in_annotation from an earlier step;
jobState is read from a query that may still be serving pending when the effect fires, so the guard's jobState === "pending" check passes against a stale value.
Both are timing-dependent, which matches the intermittency and matches it appearing on a loaded CI runner rather than locally.
Why it matters more than an ordinary flake
The real-server cycle suite was three separate times the only suite to catch a regression during the 2026-08 run (#306, #308, #309 — see #314). A suite with that hit rate must be trustworthy, because the reflex for a flaky required check is to stop reading it.
It is also a required check on the main ruleset, so a flake blocks merges until somebody re-runs it by hand.
Suggested direction
Not "add a retry". Two candidates worth looking at first:
Do not surface an opening refusal that the page has already recovered from. If the batch is in_annotation and the job is in_progress by the time the answer arrives, an INVALID_TRANSITION from a start effect is already-done, not a failure — the page is in the state the effect wanted. Distinguishing "refused because it was already true" from "refused because it may not" would clear the symptom honestly rather than hiding it.
Symptom
browser cycle (chromium)— the full-cycle Playwright run against a real server — fails intermittently atcycle/cycle.spec.ts:321:Observed twice on 2026-08-04/05:
Diagnosis
The rendered string is
refusalProseforINVALID_TRANSITION. That code cannot come from the save itself:AnnotationService.addrefuses a closed batch withBatchNotInAnnotation, neverInvalidTransition.It comes from the auto-start effects.
AnnotationPage'sSaveStaterenderssave.errorand falls back toopeningRefusal— the state that catches a refusedPOST /batches/{id}/startorPOST /jobs/{id}/startfired on open (#300, and the StrictMode ref-guard added there). So the sequence is: the batch or the job was already started when the effect sent the transition, the kernel answeredINVALID_TRANSITION, and the refusal landed in the same slot the save reports through — where the spec was waiting for "Saved".Likely races:
in_annotationfrom an earlier step;jobStateis read from a query that may still be servingpendingwhen the effect fires, so the guard'sjobState === "pending"check passes against a stale value.Both are timing-dependent, which matches the intermittency and matches it appearing on a loaded CI runner rather than locally.
Why it matters more than an ordinary flake
The real-server cycle suite was three separate times the only suite to catch a regression during the 2026-08 run (#306, #308, #309 — see #314). A suite with that hit rate must be trustworthy, because the reflex for a flaky required check is to stop reading it.
It is also a required check on the
mainruleset, so a flake blocks merges until somebody re-runs it by hand.Suggested direction
Not "add a retry". Two candidates worth looking at first:
in_annotationand the job isin_progressby the time the answer arrives, anINVALID_TRANSITIONfrom a start effect is already-done, not a failure — the page is in the state the effect wanted. Distinguishing "refused because it was already true" from "refused because it may not" would clear the symptom honestly rather than hiding it.Whichever is chosen, the cycle spec's assertion at :321 stays as it is: waiting for "Saved" after a save is the right thing to assert.
Notes
check.shrun the browser suites) — a flaky suite is an argument for fixing the flake, not for leaving the suite out.