RocketChat / RocketChat/Rocket.Chat

Duplicate rooms in the sidebar when a cached record _id is not a string

Open
#42,109 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

type: bug
Dominant language
TypeScript
Stars
46.1k
Forks
13.9k
Avg merge
3d 3h
Merged PRs (30d)
130

Description

Description:

The cached stores (apps/meteor/client/lib/cachedStores) key their records by the raw _id in a Map. When a record carries another representation of its id — a BSON ObjectId, or the raw bytes of an EJSON binary — that value is not a stable key, so each merge stores a new entry instead of replacing the previous one. The store and the localForage cache grow a duplicate per merge, and the sidebar lists the same room once per copy. They only go away when the user clears their site data.

I hit this on 8.6.0 with a subscription whose _id is a BSON ObjectId (our own doing: an internal script wrote channel memberships straight into MongoDB). It arrives over DDP as {"_id":{"buffer":{"$binary":"an/HuDdrg3tLiqbD"}}}, while the other subscriptions arrive as 24-character strings. The server normalizes ids — BaseRaw.insertOne rewrites a non-string _id with toHexString() — but the client cache does not: records.set(record._id, record) uses the value as it arrives, and CachedStore's hasId guard only checks that the key exists while its type says _id: string.

I fixed our data. Reporting the client side because the cache should not break on it, and the same shape can come from imports, migrations or restores.

Steps to reproduce:
  1. Insert a subscription with a BSON ObjectId, e.g. db.rocketchat_subscription.insertOne({ _id: new ObjectId(), rid: <room id>, ... })
  2. Log in as that user, open the room and click "View thread" on any message.
  3. Every merge adds one identical row to the sidebar and one record to the subscriptions entry of IndexedDB. Measured sidebar rows: 1 -> 2 -> 4 -> 6; cached records 49 -> 50, of which those with a binary _id 13 -> 14.
Expected behavior:

The room is listed once. The same document replaces the previous entry in the store.

Actual behavior:

The room is listed once per merge, and the extra rows survive reloads until the local cache is cleared manually.

Server Setup Information:
  • Version of Rocket.Chat Server: 8.6.0 (also present on develop: records.set(record._id, record))
  • Deployment Method: docker
  • NodeJS Version: 22.22.3
  • MongoDB Version: 8.0.26
Client Setup Information
  • Desktop App or Browser Version: Chrome (headless, empty profile)
  • Operating System: Windows
Relevant logs:

Nothing server-side; the effect is client-only.

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 in apps/meteor/client/lib/cachedStores, especially the records.set(record._id, record) path and CachedStore's hasId guard. Reproduce the merge with a BSON ObjectId or EJSON binary _id, then verify that repeated merges replace one cached record, the sidebar shows one room, and the subscriptions IndexedDB entry does not grow duplicates.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.