microsoft / microsoft/FluidFramework

Client refresh before summary causes stale state to overwrite live state for all connected clients

Open
#26,829 16 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug community-contribution
Dominant language
TypeScript
Stars
4.9k
Forks
586
Avg merge
1d 15h
Merged PRs (30d)
146

Description

Describe the bug

We're building a collaborative diagramming app using Fluid Framework with SharedTree and React Flow. When a client refreshes before the Fluid service has created a new summary (which we observe takes ~15 seconds after changes), the refreshing client loads stale data from the last summary. During our app's initialization, we write reconciled state back to the SharedTree, which generates new ops that overwrite the correct live state — causing all connected clients to see nodes jump back to their old positions.

We believe there may be a gap in our understanding of the correct initialization pattern, or possibly an issue with how summaries are timed. We'd appreciate guidance on the recommended approach.

Environment

  • fluid-framework: 2.2.0
  • @fluidframework/azure-client: 2.2.0
  • @fluidframework/protocol-definitions: 3.2.0
  • SharedTree (not SharedMap)
  • Frontend: React + React Flow (node-based diagram editor)
  • Hosting: Azure Fluid Relay
To Reproduce

Scenario 1: Refresh BEFORE summary (~0–14s after edit) — Bug

  1. User A and User B are in the same project (same Fluid container)
  2. User B drags a node to a new position → the position is written to SharedTree → User A sees the node move in real-time (confirmed: ops are being sent and received correctly)
  3. User B refreshes the page before a new summary is created (~15s window)
  4. User B's new client loads the container from the last summary, which does not include the position change
  5. During our app initialization, we reconcile backend data with Fluid state and write the reconciled nodes back to SharedTree (via Tree.runTransaction)
  6. These writes generate new ops containing the stale positions from the old summary
  7. Result: User B sees the node in its old position. User A also sees the node jump back to the old position (because the stale ops from User B's new client overwrite the correct state)

Scenario 2: Refresh AFTER summary (~15s+ after edit) — Works correctly

  1. Same setup as above
  2. User B drags a node → User A sees the move
  3. We observe the network tab and wait for a summaries call to the Fluid service (~15 seconds after the edit)
  4. User B refreshes the page after the summary call completes
  5. User B's new client loads the container from the new summary, which includes the correct position
  6. Our app initialization writes the same (correct) positions back to SharedTree — effectively a no-op
  7. Result: Both users see the correct position. Everything works as expected.
Expected behavior

When a client refreshes and reconnects to an existing Fluid container, the container should process all trailing ops (ops sequenced after the last summary) during the CatchingUp phase before the app can read or write state. Once the container reaches ConnectionState.Connected, the SharedTree state should reflect the full up-to-date state — including changes made by other clients that are not yet captured in a summary. This would ensure that any app-level reconciliation logic that reads from and writes back to the SharedTree does not inadvertently overwrite recent collaborative changes with stale summary data.

Specifically, we would expect:
A client loading from a stale summary should transparently catch up to the latest sequenced state before any local reads/writes take effect
If our app writes values back to SharedTree that match the already-caught-up state, those writes should be no-ops (no new ops generated)
Other connected clients should never see state revert due to a peer refreshing

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the container lifecycle from CatchingUp to ConnectionState.Connected, then trace the initialization path that reads SharedTree and writes through Tree.runTransaction. Reproduce refreshes before and after the summary window; done means determining whether trailing ops are applied before reconciliation and documenting the correct behavior or a confirmed framework defect.

Written by the indexing model from the issue text.

Assessment

Tech stack
azure, react, typescript
Domain
distributed-systems, frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.