diff --git a/lib/reducers/message-reducer.js b/lib/reducers/message-reducer.js
--- a/lib/reducers/message-reducer.js
+++ b/lib/reducers/message-reducer.js
@@ -136,7 +136,6 @@
   );
 }
 
-// eslint-disable-next-line no-unused-vars
 function assertMessageStoreThreadsAreEqual(
   processedMessageStore: MessageStore,
   expectedMessageStore: MessageStore,
@@ -253,6 +252,8 @@
 
   const threads = {};
   const reassignedThreadIDs = [];
+  const updatedThreads = {};
+  const threadsToRemove = [];
   for (const threadID in messageStore.threads) {
     const threadMessageInfo = messageStore.threads[threadID];
     const newThreadID = pendingToRealizedThreadIDs.get(threadID);
@@ -264,6 +265,8 @@
     if (!realizedThread) {
       reassignedThreadIDs.push(newThreadID);
       threads[newThreadID] = threadMessageInfo;
+      updatedThreads[newThreadID] = threadMessageInfo;
+      threadsToRemove.push(threadID);
       continue;
     }
     threads[newThreadID] = mergeThreadMessageInfos(
@@ -271,7 +274,22 @@
       realizedThread,
       messages,
     );
+    updatedThreads[newThreadID] = threads[newThreadID];
   }
+  if (threadsToRemove.length) {
+    messageStoreOperations.push({
+      type: 'remove_threads',
+      payload: {
+        ids: threadsToRemove,
+      },
+    });
+  }
+  messageStoreOperations.push({
+    type: 'replace_threads',
+    payload: {
+      threads: updatedThreads,
+    },
+  });
 
   return {
     messageStoreOperations,
@@ -295,6 +313,7 @@
   newMessageInfos: $ReadOnlyArray<RawMessageInfo>,
   truncationStatus: { [threadID: string]: MessageTruncationStatus },
   threadInfos: { +[threadID: string]: RawThreadInfo },
+  actionType: string,
 ): MergeNewMessagesResult {
   const {
     messageStoreOperations: updateWithLatestThreadInfosOps,
@@ -307,6 +326,12 @@
     updateWithLatestThreadInfosOps,
   );
 
+  assertMessageStoreThreadsAreEqual(
+    messageStoreAfterUpdateOps,
+    messageStoreUpdatedWithLatestThreadInfos,
+    `${actionType} | reassignment and filtering`,
+  );
+
   const updatedMessageStore = {
     ...messageStoreUpdatedWithLatestThreadInfos,
     messages: messageStoreAfterUpdateOps.messages,
@@ -444,16 +469,18 @@
   const oldMessageInfosToCombine = [];
   const threadsThatNeedMessageIDsResorted = [];
   const local = {};
+  const updatedThreads = {};
   const threads = _flow(
     _mapValuesWithKeys((messageIDs: string[], threadID: string) => {
       const oldThread = updatedMessageStore.threads[threadID];
       const truncate = truncationStatus[threadID];
       if (!oldThread) {
-        return {
+        updatedThreads[threadID] = {
           ...newThread(),
           messageIDs,
           startReached: truncate === messageTruncationStatus.EXHAUSTIVE,
         };
+        return updatedThreads[threadID];
       }
       let oldMessageIDsUnchanged = true;
       const oldMessageIDs = oldThread.messageIDs.map(oldID => {
@@ -473,12 +500,13 @@
           type: 'remove_messages_for_threads',
           payload: { threadIDs: [threadID] },
         });
-        return {
+        updatedThreads[threadID] = {
           messageIDs,
           startReached: false,
           lastNavigatedTo: oldThread.lastNavigatedTo,
           lastPruned: oldThread.lastPruned,
         };
+        return updatedThreads[threadID];
       }
       const oldNotInNew = _difference(oldMessageIDs)(messageIDs);
       for (const id of oldNotInNew) {
@@ -497,12 +525,13 @@
         if (startReached === oldThread.startReached && oldMessageIDsUnchanged) {
           return oldThread;
         }
-        return {
+        updatedThreads[threadID] = {
           messageIDs: oldMessageIDs,
           startReached,
           lastNavigatedTo: oldThread.lastNavigatedTo,
           lastPruned: oldThread.lastPruned,
         };
+        return updatedThreads[threadID];
       }
       const mergedMessageIDs = [...messageIDs, ...oldNotInNew];
       threadsThatNeedMessageIDsResorted.push(threadID);
@@ -529,6 +558,8 @@
       };
     }
     threads[threadID] = thread;
+    updatedThreads[threadID] = thread;
+
     for (const id of thread.messageIDs) {
       const messageInfo = updatedMessageStore.messages[id];
       if (messageInfo) {
@@ -565,6 +596,7 @@
     threads[threadID].messageIDs = sortMessageIDs(messages)(
       threads[threadID].messageIDs,
     );
+    updatedThreads[threadID] = threads[threadID];
   }
 
   const currentAsOf = Math.max(
@@ -572,22 +604,37 @@
     updatedMessageStore.currentAsOf,
   );
 
+  newMessageOps.push({
+    type: 'replace_threads',
+    payload: {
+      threads: updatedThreads,
+    },
+  });
+
   const processedMessageStore = processMessageStoreOperations(
     updatedMessageStore,
     newMessageOps,
   );
 
+  const messageStore = {
+    messages: processedMessageStore.messages,
+    threads,
+    local,
+    currentAsOf,
+  };
+
+  assertMessageStoreThreadsAreEqual(
+    processedMessageStore,
+    messageStore,
+    `${actionType} | processed`,
+  );
+
   return {
     messageStoreOperations: [
       ...updateWithLatestThreadInfosOps,
       ...newMessageOps,
     ],
-    messageStore: {
-      messages: processedMessageStore.messages,
-      threads,
-      local,
-      currentAsOf,
-    },
+    messageStore,
   };
 }
 
@@ -628,6 +675,7 @@
     }
   }
 
+  const updatedThreads = {};
   for (const threadID in threadInfos) {
     const threadInfo = threadInfos[threadID];
     if (
@@ -635,9 +683,22 @@
       !filteredThreads[threadID]
     ) {
       filteredThreads[threadID] = newThread();
+      updatedThreads[threadID] = filteredThreads[threadID];
     }
   }
 
+  messageStoreOperations.push({
+    type: 'remove_threads',
+    payload: { ids: threadsToRemoveMessagesFrom },
+  });
+
+  messageStoreOperations.push({
+    type: 'replace_threads',
+    payload: {
+      threads: updatedThreads,
+    },
+  });
+
   messageStoreOperations.push({
     type: 'remove_messages_for_threads',
     payload: { threadIDs: threadsToRemoveMessagesFrom },
@@ -768,6 +829,7 @@
       messagesResult.rawMessageInfos,
       messagesResult.truncationStatuses,
       newThreadInfos,
+      action.type,
     );
   } else if (action.type === processUpdatesActionType) {
     if (action.payload.updatesResult.newUpdates.length === 0) {
@@ -785,6 +847,7 @@
         messagesResult.rawMessageInfos,
         messagesResult.truncationStatuses,
         newThreadInfos,
+        action.type,
       );
     return {
       messageStoreOperations,
@@ -805,6 +868,7 @@
       messagesResult.rawMessageInfos,
       messagesResult.truncationStatuses,
       newThreadInfos,
+      action.type,
     );
   } else if (
     action.type === fetchSingleMostRecentMessagesFromThreadsActionTypes.success
@@ -814,6 +878,7 @@
       action.payload.rawMessageInfos,
       action.payload.truncationStatuses,
       newThreadInfos,
+      action.type,
     );
   } else if (
     action.type === fetchMessagesBeforeCursorActionTypes.success ||
@@ -824,6 +889,7 @@
       action.payload.rawMessageInfos,
       { [action.payload.threadID]: action.payload.truncationStatus },
       newThreadInfos,
+      action.type,
     );
   } else if (
     action.type === logOutActionTypes.success ||
@@ -857,6 +923,7 @@
       messagesResult.rawMessageInfos,
       messagesResult.truncationStatuses,
       newThreadInfos,
+      action.type,
     );
   } else if (action.type === sendMessageReportActionTypes.success) {
     return mergeNewMessages(
@@ -867,6 +934,7 @@
           messageTruncationStatus.UNCHANGED,
       },
       newThreadInfos,
+      action.type,
     );
   } else if (action.type === registerActionTypes.success) {
     const truncationStatuses = {};
@@ -879,6 +947,7 @@
       action.payload.rawMessageInfos,
       truncationStatuses,
       newThreadInfos,
+      action.type,
     );
   } else if (
     action.type === changeThreadSettingsActionTypes.success ||
@@ -894,6 +963,7 @@
       action.payload.newMessageInfos,
       { [action.payload.threadID]: messageTruncationStatus.UNCHANGED },
       newThreadInfos,
+      action.type,
     );
   } else if (action.type === deleteEntryActionTypes.success) {
     const payload = action.payload;
@@ -903,6 +973,7 @@
         payload.newMessageInfos,
         { [payload.threadID]: messageTruncationStatus.UNCHANGED },
         newThreadInfos,
+        action.type,
       );
     }
   } else if (action.type === joinThreadActionTypes.success) {
@@ -915,6 +986,7 @@
       messagesResult.rawMessageInfos,
       messagesResult.truncationStatuses,
       newThreadInfos,
+      action.type,
     );
   } else if (action.type === sendEditMessageActionTypes.success) {
     const { newMessageInfos } = action.payload;
@@ -928,6 +1000,7 @@
       newMessageInfos,
       truncationStatuses,
       newThreadInfos,
+      action.type,
     );
   } else if (
     action.type === sendTextMessageActionTypes.started ||
@@ -1151,6 +1224,7 @@
         action.payload.rawMessageInfos,
         truncationStatuses,
         newThreadInfos,
+        action.type,
       );
     return {
       messageStoreOperations,
@@ -1424,6 +1498,12 @@
       messageStoreOperations,
     );
 
+    assertMessageStoreThreadsAreEqual(
+      processedMessageStore,
+      messageStoreAfterReassignment,
+      action.type,
+    );
+
     return {
       messageStoreOperations,
       messageStore: {