RocketChat/Rocket.Chat · error · Error

Invalid main message

Error message

Invalid main message

What it means

Thrown inside the threadMainMessage React Query queryFn when getMessage(tmid) returns a falsy value. This fetches the root/main message of a thread (identified by tmid — the thread message ID, which is the _id of the message that started the thread). If the fetch returns null/undefined, the query fails with 'Invalid main message', preventing the thread view from rendering with incomplete data.

Source

Thrown at apps/meteor/client/views/room/contextualBar/Threads/hooks/useThreadMainMessageQuery.ts:95

	const queryClient = useQueryClient();
	const unsubscribeRef = useRef<(() => void) | undefined>(undefined);

	useEffect(() => {
		return () => {
			unsubscribeRef.current?.();
			unsubscribeRef.current = undefined;
		};
	}, [tmid]);

	return useQuery({
		queryKey: roomsQueryKeys.threadMainMessage(room._id, tmid),

		queryFn: async ({ queryKey }) => {
			const mainMessage = await getMessage(tmid);

			if (!mainMessage) {
				throw new Error('Invalid main message');
			}

			const debouncedInvalidate = withDebouncing({ wait: 10000 })(() => {
				queryClient.invalidateQueries({ queryKey, exact: true });
			});

			unsubscribeRef.current =
				unsubscribeRef.current ||
				subscribeToMessage(mainMessage, {
					onMutate: async (message) => {
						const msg = await onClientMessageReceived(message);
						queryClient.setQueryData(queryKey, () => msg);
						debouncedInvalidate();
					},
					onDelete: () => {
						onDelete?.();
						queryClient.invalidateQueries({ queryKey, exact: true });
					},

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Verify the thread main message exists and is accessible before opening the thread view.
  2. Catch the error in the query consumer and show a 'thread main message unavailable' state.
  3. Refresh thread metadata if the main message might have been deleted.
  4. Check user permissions on the thread's parent room before querying.

Example fix

// before
const { data } = useThreadMainMessageQuery({ room, tmid });
// after
const { data, isError } = useThreadMainMessageQuery({ room, tmid });
if (isError) {
  return <ThreadUnavailable />;
}
Defensive patterns

Strategy: try-catch

Validate before calling

const mainMessage = await getMessage(tmid);
if (!mainMessage) {
  // do not open thread view, or show fallback
  return;
}

Type guard

const isThreadMainMessageValid = (msg: unknown): msg is IMessage =>
  Boolean(msg && typeof msg === 'object' && '_id' in msg);

Try / catch

const { data, isError } = useThreadMainMessageQuery({ room, tmid });
if (isError) {
  return <ThreadMainMessageUnavailable />;
}

Prevention

When it happens

Trigger: The thread's main message was deleted after the thread was created. The user does not have permission to read the main message. The tmid is invalid or refers to a message in a different room. Network error causes getMessage to return null. The main message is in a room the user left.

Common situations: Thread root message was deleted by a moderator but replies remain. User was removed from the room where the thread started. Stale tmid from cached thread metadata after room data changed. API returns null due to permission checks.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12). Data as JSON: /api/errors/02c1d96e92be27ae. Report an issue: GitHub.