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
@@ -60,7 +60,6 @@
 import { pendingToRealizedThreadIDsSelector } from '../selectors/thread-selectors.js';
 import {
   messageID,
-  combineTruncationStatuses,
   sortMessageInfoList,
   sortMessageIDs,
   mergeThreadMessageInfos,
@@ -72,6 +71,7 @@
 } from '../shared/thread-utils.js';
 import threadWatcher from '../shared/thread-watcher.js';
 import { unshimMessageInfos } from '../shared/unshim-utils.js';
+import { updateSpecs } from '../shared/updates/update-specs.js';
 import type { Media, Image } from '../types/media-types.js';
 import { messageTypes } from '../types/message-types-enum.js';
 import {
@@ -95,7 +95,6 @@
 import { updateThreadLastNavigatedActionType } from '../types/thread-activity-types.js';
 import { threadPermissions } from '../types/thread-permission-types.js';
 import { type RawThreadInfo } from '../types/thread-types.js';
-import { updateTypes } from '../types/update-types-enum.js';
 import {
   type ClientUpdateInfo,
   processUpdatesActionType,
@@ -1778,27 +1777,23 @@
   newUpdates: $ReadOnlyArray<ClientUpdateInfo>,
   truncationStatuses?: MessageTruncationStatuses,
 ): MergedUpdatesWithMessages {
-  const messageIDs = new Set(messageInfos.map(messageInfo => messageInfo.id));
+  const messageIDs = new Set(
+    messageInfos.map(messageInfo => messageInfo.id).filter(Boolean),
+  );
   const mergedMessageInfos = [...messageInfos];
   const mergedTruncationStatuses = { ...truncationStatuses };
 
-  for (const updateInfo of newUpdates) {
-    if (updateInfo.type !== updateTypes.JOIN_THREAD) {
+  for (const update of newUpdates) {
+    const { mergeMessageInfosAndTruncationStatuses } = updateSpecs[update.type];
+    if (!mergeMessageInfosAndTruncationStatuses) {
       continue;
     }
-    for (const messageInfo of updateInfo.rawMessageInfos) {
-      if (messageIDs.has(messageInfo.id)) {
-        continue;
-      }
-      mergedMessageInfos.push(messageInfo);
-      messageIDs.add(messageInfo.id);
-    }
-
-    mergedTruncationStatuses[updateInfo.threadInfo.id] =
-      combineTruncationStatuses(
-        updateInfo.truncationStatus,
-        mergedTruncationStatuses[updateInfo.threadInfo.id],
-      );
+    mergeMessageInfosAndTruncationStatuses(
+      messageIDs,
+      mergedMessageInfos,
+      mergedTruncationStatuses,
+      update,
+    );
   }
   return {
     rawMessageInfos: mergedMessageInfos,
diff --git a/lib/shared/updates/join-thread-spec.js b/lib/shared/updates/join-thread-spec.js
--- a/lib/shared/updates/join-thread-spec.js
+++ b/lib/shared/updates/join-thread-spec.js
@@ -4,8 +4,13 @@
 
 import type { UpdateSpec } from './update-spec.js';
 import type { RawEntryInfo } from '../../types/entry-types.js';
+import type {
+  RawMessageInfo,
+  MessageTruncationStatuses,
+} from '../../types/message-types.js';
 import type { RawThreadInfos } from '../../types/thread-types.js';
 import type { ThreadJoinUpdateInfo } from '../../types/update-types.js';
+import { combineTruncationStatuses } from '../message-utils.js';
 import { threadInFilterList } from '../thread-utils.js';
 
 export const joinThreadSpec: UpdateSpec<ThreadJoinUpdateInfo> = Object.freeze({
@@ -55,4 +60,24 @@
   getRawMessageInfos(update: ThreadJoinUpdateInfo) {
     return update.rawMessageInfos;
   },
+  mergeMessageInfosAndTruncationStatuses(
+    messageIDs: Set<string>,
+    messageInfos: Array<RawMessageInfo>,
+    truncationStatuses: MessageTruncationStatuses,
+    update: ThreadJoinUpdateInfo,
+  ) {
+    for (const messageInfo of update.rawMessageInfos) {
+      const messageID = messageInfo.id;
+      if (!messageID || messageIDs.has(messageID)) {
+        continue;
+      }
+      messageInfos.push(messageInfo);
+      messageIDs.add(messageID);
+    }
+
+    truncationStatuses[update.threadInfo.id] = combineTruncationStatuses(
+      update.truncationStatus,
+      truncationStatuses[update.threadInfo.id],
+    );
+  },
 });
diff --git a/lib/shared/updates/update-spec.js b/lib/shared/updates/update-spec.js
--- a/lib/shared/updates/update-spec.js
+++ b/lib/shared/updates/update-spec.js
@@ -2,7 +2,10 @@
 
 import type { ThreadStoreOperation } from '../../ops/thread-store-ops.js';
 import type { RawEntryInfo } from '../../types/entry-types.js';
-import type { RawMessageInfo } from '../../types/message-types.js';
+import type {
+  RawMessageInfo,
+  MessageTruncationStatuses,
+} from '../../types/message-types.js';
 import type { RawThreadInfos } from '../../types/thread-types.js';
 import type { ClientUpdateInfo } from '../../types/update-types.js';
 import type { CurrentUserInfo, UserInfos } from '../../types/user-types.js';
@@ -27,4 +30,10 @@
     update: UpdateInfo,
   ) => $ReadOnlySet<string>,
   +getRawMessageInfos?: (update: UpdateInfo) => $ReadOnlyArray<RawMessageInfo>,
+  +mergeMessageInfosAndTruncationStatuses?: (
+    messageIDs: Set<string>,
+    messageInfos: Array<RawMessageInfo>,
+    truncationStatuses: MessageTruncationStatuses,
+    update: UpdateInfo,
+  ) => void,
 };