diff --git a/lib/reducers/master-reducer.js b/lib/reducers/master-reducer.js
--- a/lib/reducers/master-reducer.js
+++ b/lib/reducers/master-reducer.js
@@ -27,6 +27,7 @@
   registerActionTypes,
   logInActionTypes,
 } from '../actions/user-actions.js';
+import { isStaff } from '../shared/staff-utils.js';
 import type { BaseNavInfo } from '../types/nav-types.js';
 import type { BaseAppState, BaseAction } from '../types/redux-types.js';
 import {
@@ -34,11 +35,13 @@
   incrementalStateSyncActionType,
 } from '../types/socket-types.js';
 import type { StoreOperations } from '../types/store-ops-types.js';
+import { isDev } from '../utils/dev-utils.js';
 import { ashoatKeyserverID } from '../utils/validation-utils.js';
 
 export default function baseReducer<N: BaseNavInfo, T: BaseAppState<N>>(
   state: T,
   action: BaseAction,
+  onStateDifference: (message: string) => mixed,
 ): { state: T, storeOperations: StoreOperations } {
   const { threadStore, newThreadInconsistencies, threadStoreOperations } =
     reduceThreadInfos(state.threadStore, action);
@@ -50,9 +53,19 @@
     threadInfos,
   );
 
+  const onStateDifferenceForStaff = (message: string) => {
+    const isCurrentUserStaff = state.currentUserInfo?.id
+      ? isStaff(state.currentUserInfo.id)
+      : false;
+    if (isCurrentUserStaff || isDev) {
+      onStateDifference(message);
+    }
+  };
+
   const [userStore, newUserInconsistencies] = reduceUserInfos(
     state.userStore,
     action,
+    onStateDifferenceForStaff,
   );
 
   const newInconsistencies = [
diff --git a/lib/reducers/user-reducer.js b/lib/reducers/user-reducer.js
--- a/lib/reducers/user-reducer.js
+++ b/lib/reducers/user-reducer.js
@@ -33,6 +33,8 @@
 import { processUpdatesActionType } from '../types/update-types.js';
 import type { CurrentUserInfo, UserStore } from '../types/user-types.js';
 import { setNewSessionActionType } from '../utils/action-utils.js';
+import { getMessageForException } from '../utils/errors.js';
+import { assertObjectsAreEqual } from '../utils/objects.js';
 
 function reduceCurrentUserInfo(
   state: ?CurrentUserInfo,
@@ -120,9 +122,37 @@
   return state;
 }
 
+// eslint-disable-next-line no-unused-vars
+function assertUserStoresAreEqual(
+  processedUserStore: UserStore,
+  expectedUserStore: UserStore,
+  location: string,
+  onStateDifference: (message: string) => mixed,
+) {
+  try {
+    assertObjectsAreEqual(
+      processedUserStore,
+      expectedUserStore,
+      `UserStore - ${location}`,
+    );
+  } catch (e) {
+    console.log(
+      'Error processing UserStore ops',
+      processedUserStore,
+      expectedUserStore,
+    );
+    const message = `Error processing UserStore ops ${
+      getMessageForException(e) ?? '{no exception message}'
+    }`;
+    onStateDifference(message);
+  }
+}
+
 function reduceUserInfos(
   state: UserStore,
   action: BaseAction,
+  // eslint-disable-next-line no-unused-vars
+  onStateDifference: (message: string) => mixed,
 ): [UserStore, $ReadOnlyArray<ClientUserInconsistencyReportCreationRequest>] {
   if (
     action.type === joinThreadActionTypes.success ||
diff --git a/lib/utils/objects.js b/lib/utils/objects.js
--- a/lib/utils/objects.js
+++ b/lib/utils/objects.js
@@ -119,8 +119,7 @@
   const dataProcessedButNotExpected = deepDiff(processedObject, expectedObject);
   const dataExpectedButNotProcessed = deepDiff(expectedObject, processedObject);
 
-  invariant(
-    false,
+  throw new Error(
     `${message}: Objects should be equal.` +
       ` Data processed but not expected:` +
       ` ${JSON.stringify(dataProcessedButNotExpected)}` +
diff --git a/native/redux/redux-debug-utils.js b/native/redux/redux-debug-utils.js
new file mode 100644
--- /dev/null
+++ b/native/redux/redux-debug-utils.js
@@ -0,0 +1,9 @@
+// @flow
+
+import { Alert } from 'react-native';
+
+function onStateDifference(message: string) {
+  Alert.alert('State difference found', message);
+}
+
+export { onStateDifference };
diff --git a/native/redux/redux-setup.js b/native/redux/redux-setup.js
--- a/native/redux/redux-setup.js
+++ b/native/redux/redux-setup.js
@@ -40,6 +40,7 @@
 import { defaultState } from './default-state.js';
 import { remoteReduxDevServerConfig } from './dev-tools.js';
 import { persistConfig, setPersistor } from './persist.js';
+import { onStateDifference } from './redux-debug-utils.js';
 import { processDBStoreOperations } from './redux-utils.js';
 import type { AppState } from './state-types.js';
 import { getGlobalNavContext } from '../navigation/icky-global.js';
@@ -220,7 +221,11 @@
     }
   }
 
-  const baseReducerResult = baseReducer(state, (action: BaseAction));
+  const baseReducerResult = baseReducer(
+    state,
+    (action: BaseAction),
+    onStateDifference,
+  );
   state = baseReducerResult.state;
 
   const { storeOperations } = baseReducerResult;
diff --git a/web/redux/redux-debug-utils.js b/web/redux/redux-debug-utils.js
new file mode 100644
--- /dev/null
+++ b/web/redux/redux-debug-utils.js
@@ -0,0 +1,7 @@
+// @flow
+
+function onStateDifference(message: string) {
+  window.alert(message);
+}
+
+export { onStateDifference };
diff --git a/web/redux/redux-setup.js b/web/redux/redux-setup.js
--- a/web/redux/redux-setup.js
+++ b/web/redux/redux-setup.js
@@ -54,6 +54,7 @@
   setPickledPrimaryAccount,
 } from './crypto-store-reducer.js';
 import reduceNavInfo from './nav-reducer.js';
+import { onStateDifference } from './redux-debug-utils.js';
 import { getVisibility } from './visibility.js';
 import { getDatabaseModule } from '../database/database-module-provider.js';
 import { activeThreadSelector } from '../selectors/nav-selectors.js';
@@ -200,7 +201,7 @@
     action.type !== setPickledPrimaryAccount &&
     action.type !== setPickledNotificationAccount
   ) {
-    const baseReducerResult = baseReducer(state, action);
+    const baseReducerResult = baseReducer(state, action, onStateDifference);
     state = baseReducerResult.state;
 
     const {