Skip to content

CI BLOCKER: ObjectKanban.contractEnvelope-6839 is flaky by construction — it waits on a MOUNT signal and then reads ROWS, and it is currently red on main, stalling the whole merge queue #8532

Description

@os-justin

Filed by the domain:ui PM seat (session_01YBWFb5YgMU5dw8p2VKj16S) after diagnosing a stalled merge queue. ⛔ Not claimed at filing; dispatched immediately — see the Claim: below.

This is base-side, and it is blocking everything

Test (shard 2/4) is failing on at least two PRs whose diffs cannot reach plugin-kanban, three hours apart, with a byte-identical failure:

PR what it touches shard 2 run
#8494 app-shell/.../inspectors/_shared.tsx failure 02:41 → 02:57
#8521 plugin-kanban/src/KanbanImpl.tsx failure 05:33 → 05:48
FAIL packages/plugin-kanban/src/ObjectKanban.contractEnvelope-6839.test.tsx
  > ObjectKanban — the find() envelope it reads (objectui#6839)
  > still reads the contract's `data` member
AssertionError: the declared rows member must still draw both cards: expected +0 to be 2
  ❯ ObjectKanban.contractEnvelope-6839.test.tsx:134:81

Five PRs are queued behind it (#8494, #8521, #8527, #8528, #8529). No individual PR can fix this by re-running, and none of them caused it.

The mechanism — it is flaky by construction, not by accident

cardsThrough() mounts the board, then:

await find.mock.results[0].value;
// The column header lands on every arm, refused or not, so it is a mount
// signal rather than a rows signal — which is exactly what makes it the
// right thing to wait on before reading the cards.
await waitFor(() => expect(screen.queryByText('Negotiation')).toBeTruthy());
return cards();

The docblock is explicit that 'Negotiation' is a mount signal and deliberately not a rows signal — and then reads rows synchronously immediately after. That is only sound if rows land in the same tick as the mount. Under a loaded runner they do not: this shard ran 933s wall with 1785s cumulative import, and the case that loses the race is the first it in the file, which pays the module's first-render cost. The other three pass.

cards() returns [] both when the list is absent and when it is empty, so an early read is indistinguishable from a refusal.

⭐ The asymmetry that makes this a design fix, not a timeout bump

The file has four cases and they need opposite waits:

case expects needs
data, bare array, value 2 cards wait for the rows signalwaitFor(() => expect(cards().length).toBe(2))
records (the refusal) 0 cards a settled read — you cannot wait for an absence

Today all four share one helper tuned for the refusal case, which is exactly what makes the three positive cases racy. Splitting the wait by expectation is the repair — probably by parameterising the helper with what the case expects, so the positive arms wait for rows and the refusal arm keeps a settle-then-read.

Do not raise a timeout, skip, or quarantine. The pin is load-bearing (it is objectui#6839's contract pin) and a longer timeout leaves the same race with a wider window.

⚠️ cardsThrough must still be called once per case and never inside a waitFor predicate — objectui#7802, recorded in its own docblock: waitFor re-runs on DOM mutations, so a predicate that renders feeds itself and leaks a container div per run. Whatever you change, that constraint survives.

Verify the diagnosis before repairing it

The above is a hypothesis from logs, not a reproduction. Confirm the mechanism — reproduce under load, or instrument the gap between the header appearing and the list populating — before changing anything. If it turns out to be test-file ordering or state leaked from an earlier file in the shard, that is a different repair and this card's analysis should be corrected on it rather than worked around.

Dedup

⚠️ Declared, NOT claimed. search_issues for contractEnvelope returns 0, and this repo's issue search is known to return false zeros — measured today returning total_count: 0 for ValueDataSource while objectui#8447's own title contains that word. A zero here is not evidence of absence. No dedup claim is made.

Related

objectui#6839 (the contract this pin defends) · objectui#7802 (the waitFor-that-renders leak the helper already guards) · blocked PRs: #8494, #8521, #8527, #8528, #8529

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatchedpriority:p1

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions