Dosu LogoDosu Logo
Ask
Join our Discord
element-webPublic
Element
Documentselement-web
Room State Synchronization
Room State Synchronization
Type
Topic
Status
Published
Created
Jul 12, 2026
Updated
Jul 12, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

Room State Synchronization#

Overview#

Room state synchronization covers how Element clients maintain consistent views of room membership, and how that state is persisted and restored across sessions. Bugs in this area typically manifest as:

  • Rooms remaining visible in the room list after a user has left
  • Call indicators persisting after a leave event (stale UI)
  • Session/crypto data loss due to IndexedDB eviction under storage pressure
  • "Missing session data" dialogs at startup

Membership State: Data Flow (matrix-js-sdk)#

Sync responses arrive in processSyncResponse() in src/sync.ts, which routes rooms into one of four paths — join, invite, leave, or knock . Each path calls injectRoomEvents() , which applies state events via liveTimeline.initialiseState() → RoomState.setStateEvents().

setStateEvents() is the central hub for membership changes: it calls getOrCreateMember(), then RoomMember.setMembershipEvent() to update the membership property and emit RoomMemberEvent.Membership when it changes . EventTimeline.addEvent() conditionally applies state events to the room's state object based on the addToState flag .

Key models:

FilePurpose
src/sync.tsSync loop; routes join/leave/invite events
src/models/room-state.tsRoomState.setStateEvents() — processes membership events
src/models/room-member.tsRoomMember.setMembershipEvent() — owns membership property
src/models/room.tsaddLiveEvents() — entry point for live event processing
src/models/event-timeline.tsaddEvent() — applies state events to RoomState

Room List Updates on Leave/Rejoin (matrix-react-sdk)#

RoomListStore listens for MatrixActions.Room.myMembership events and calls onDispatchMyMembership() . When membership changes, it triggers a RoomUpdateCause.PossibleTagChange, causing Algorithm.handleRoomUpdate() to recompute the room's effective membership via getEffectiveMembershipTag():

  • EffectiveMembership.Leave → room moves to DefaultTagID.Archived tag (removed from active lists)
  • EffectiveMembership.Invite → room moves to DefaultTagID.Invite
  • Join → room uses its normal tag from getTagsOfJoinedRoom()

Rooms that need tag changes are explicitly removed with RoomUpdateCause.RoomRemoved and re-added with RoomUpdateCause.NewRoom .

Known gap: SlidingRoomListStore has incomplete support for DefaultTagID.Archived, meaning archived room filtering may not work correctly when feature_sliding_sync is enabled .

Stale Room Visibility Bug (issue #33697)#

A confirmed reproducible issue: after leaving a room (especially during an active call), Element Web can continue showing the room and its call indicator in the room list even though the user is no longer a member . Pressing "reconnect" on the call UI always fails because the client is no longer in the room. The issue resolves on client restart and appears tied to the myMembership event not propagating cleanly in all leave-during-call paths .


Storage Persistence and Session Loss#

Room membership and sync state are persisted via IndexedDBStore (sync/session store). Profile data (display names, avatars) survives restarts only through saved presence events, persisted with a 5-minute write delay (WRITE_DELAY_MS) . On failure, the sync store degrades to in-memory operation .

Separate from the sync store, the crypto store (IndexedDBCryptoStore) holds all E2EE session data. Loss of this store means the user must re-verify their identity and loses access to prior encrypted messages .

IndexedDB Eviction Under Storage Pressure#

Browsers can silently evict non-persistent IndexedDB data under disk or memory pressure. Element detects this at startup via checkConsistency(): if mx_crypto_initialised is set in localStorage but the crypto store is empty, it surfaces the StorageEvictedDialog .

Real-world triggers include running out of disk space and browser-side eviction without user action . When eviction occurs, localStorage metadata remains intact, causing a consistency mismatch that Element detects as a missing session .

Defense mechanisms:

  1. Persistent storage request — tryPersistStorage() calls navigator.storage.persist() at login to opt the origin out of automatic eviction .
  2. Sync store worker fallback — if the dedicated Web Worker for IndexedDB fails, the store falls back to the main-thread LocalIndexedDBStoreBackend rather than failing entirely (PR #5361) .
  3. Consistency check — checkConsistency() validates both sync and crypto stores on every session restore and login .

Rust crypto: Unlike the legacy stack, the Rust crypto backend requires IndexedDB and has no fallback. If IndexedDB is inaccessible, the client cannot start .

Pickle Key / OS Keychain Issues#

On Desktop, the pickle key (used to encrypt access tokens and key the crypto store) lives in the OS keychain via Electron's safeStorage. A known bug: if the keychain is transiently unavailable, loadOrCreatePickleKey() calls createPickleKey and overwrites the still-valid ciphertext — turning a transient failure into permanent session loss. PR #33986 proposes a fix using typed SafeStorageDecryptionError . This is the root cause behind "Unable to restore session: Error decrypting secret access_token: no pickle key found" errors seen on Linux .


Key Source Files Reference#

FileRepoRole
src/sync.tsmatrix-js-sdkSync loop and room event routing
src/models/room-state.tsmatrix-js-sdkRoomState.setStateEvents() — membership state hub
src/models/room-member.tsmatrix-js-sdkRoomMember.setMembershipEvent()
src/store/indexeddb.tsmatrix-js-sdkSync/session store with worker fallback
src/stores/room-list/RoomListStore.tsmatrix-react-sdkDispatches room list updates on membership changes
src/stores/room-list/algorithms/Algorithm.tsmatrix-react-sdkTag-based room list sorting/filtering
apps/web/src/utils/StorageManager.tselement-webcheckConsistency(), tryPersistStorage()
apps/web/src/Lifecycle.tselement-webSession restore, pickle key loading
Documents
Desktop File WM Class Configuration
Desktop Window Management
Drag and Drop
E2EE Key Management
Element Call Integration
Encryption Reset
IndexedDB Crypto Store
Internationalization & Localization
Matrix Notification System
Matrix Power Levels
Matrix RTC Infrastructure
Message Delivery State
OIDC Integration
Read Receipts
Resizable Panels
Room Invite Flows
Room List Section Management
Room State Synchronization
Session Restoration
Space Panel
Unread Filter
User Profile Caching
Virtualized List Rendering
Voice Message Audio Decoding
Windows Desktop Integration