fix: e2e CI flake — goal-0017 emit fanout raced canvas-live-sync - #33
Merged
Conversation
…nding #1) Root cause, confirmed via real CI history (6/6 shard-1 failures, all after goal 0017 merged, zero before) and local reproduction (9/20 clean-canvas repeats failed with zero artificial load): goal 0017 gave every direct-mutation service its own dataevent.Emit call, so a single MCP update_workflow write now fires mill-data-changed TWICE (SnapshotDraft + UpdateWorkflow) plus a third echo from the test's own prior UI-driven CreateWorkflow. None carry payload content, so each handler independently refetches — three fetches racing meant whichever resolved last won unconditionally, letting a stale response beat an already-applied newer one and wrongly show the external-change banner on a clean canvas. Fixed in useCanvasLiveSync.ts with a monotonic request-sequence guard (the standard out-of-order-async-response fix): drop a fetch response once a newer mill-data-changed event has arrived since it was dispatched. Verified: 88 consecutive clean local repeats post-fix vs. 9/20 before it, same build. Also: canvas-live-sync.spec.ts's cleanup hardened into an outer try/finally (both tests) so a future assertion failure can't leave an undeleted workflow / unattended-MCP-writes settings for later tests in the same worker — defense-in-depth for the observed cascade, independent of the root-cause fix. resizable-table.spec.ts's one occurrence (PR #24, drag-handle bounding box) hardened with a condition-based expect.poll wait at the point of use, additive to the suite's existing retries: 1 (goal 0024 precedent, untouched). BACKLOG.md Standing #1 checked off with the full root-cause writeup; SPEC.md's realtime-lock section gets an Update note generalizing the lesson for future mill-data-changed consumers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018pkViCNAuZp2vBv2K9AbUh
alicoding
enabled auto-merge (squash)
August 12, 2026 21:28
alicoding
added a commit
that referenced
this pull request
Aug 15, 2026
…nding #1) (#33) Root cause, confirmed via real CI history (6/6 shard-1 failures, all after goal 0017 merged, zero before) and local reproduction (9/20 clean-canvas repeats failed with zero artificial load): goal 0017 gave every direct-mutation service its own dataevent.Emit call, so a single MCP update_workflow write now fires mill-data-changed TWICE (SnapshotDraft + UpdateWorkflow) plus a third echo from the test's own prior UI-driven CreateWorkflow. None carry payload content, so each handler independently refetches — three fetches racing meant whichever resolved last won unconditionally, letting a stale response beat an already-applied newer one and wrongly show the external-change banner on a clean canvas. Fixed in useCanvasLiveSync.ts with a monotonic request-sequence guard (the standard out-of-order-async-response fix): drop a fetch response once a newer mill-data-changed event has arrived since it was dispatched. Verified: 88 consecutive clean local repeats post-fix vs. 9/20 before it, same build. Also: canvas-live-sync.spec.ts's cleanup hardened into an outer try/finally (both tests) so a future assertion failure can't leave an undeleted workflow / unattended-MCP-writes settings for later tests in the same worker — defense-in-depth for the observed cascade, independent of the root-cause fix. resizable-table.spec.ts's one occurrence (PR #24, drag-handle bounding box) hardened with a condition-based expect.poll wait at the point of use, additive to the suite's existing retries: 1 (goal 0024 precedent, untouched). BACKLOG.md Standing #1 checked off with the full root-cause writeup; SPEC.md's realtime-lock section gets an Update note generalizing the lesson for future mill-data-changed consumers. Claude-Session: https://claude.ai/code/session_018pkViCNAuZp2vBv2K9AbUh Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
docs/goals/BACKLOG.mdStanding build(deps): Bump golang.org/x/oauth2 from 0.35.0 to 0.36.0 in the go-dependencies group #1): goal 0017's per-servicedataevent.Emitfanout made a single MCPupdate_workflowwrite firemill-data-changedtwice (SnapshotDraft+UpdateWorkflow), plus a third self-echo from the test's own earlierCreateWorkflow— three near-simultaneous events, each independently re-fetching, whichever resolved last won unconditionally regardless of dispatch order. A stale response could beat an already-applied newer one and wrongly show the external-change banner on a clean canvas.frontend/src/composition/useCanvasLiveSync.tswith a monotonic request-sequence guard (standard out-of-order-async-response pattern): drop a fetch response once a newer event has arrived since it was dispatched.canvas-live-sync.spec.tscleanup hardened into an outer try/finally (both tests) so a future failure can't leave an undeleted workflow / unattended-MCP-writes settings for later tests in the same shard worker.resizable-table.spec.ts's one occurrence (PR feat: inline hotkey hints (goal 0015 remainder) + real Close-tabs bindings #24) hardened with a condition-basedexpect.pollwait for the resize handle's bounding box, additive to the suite's existingretries: 1.docs/goals/BACKLOG.mdStanding build(deps): Bump golang.org/x/oauth2 from 0.35.0 to 0.36.0 in the go-dependencies group #1 checked off with the full root-cause writeup;docs/SPEC.md's realtime-lock section gets an Update note generalizing the lesson for futuremill-data-changedconsumers.Evidence
canvas-live-sync.spec.ts:151failed 6/6 times sampled across the last ~30ci.ymlruns, always shard 1, always after goal 0017 (PR goal 0017: real-time surfaces audit — direct-UI/service mutations now emit mill-data-changed #16) merged, zero occurrences before. Every occurrence co-occurred with aconfigure-lists.spec.tsflake in the same run (recovered on Playwright's own retry every time — a resource-contention side effect of the extra retry overhead, not a separate state-cascade bug).mill-data-changedarrivals per run.Test plan
go vet,golangci-lint,go test ./... -race -cover,go build(desktop + server),npm run boundaries,npm run lint,npm run test(vitest),tsc --noEmitcanvas-live-sync.spec.ts+resizable-table.spec.tsrun--repeat-each=5, 0 failures🤖 Generated with Claude Code
https://claude.ai/code/session_018pkViCNAuZp2vBv2K9AbUh