RocketChat/Rocket.Chat · error · Meteor.Error

error-cursor-and-lastUpdate-conflict

error-cursor-and-lastUpdate-conflict

Error message

The attributes "next", "previous" and "lastUpdate" cannot be used together

What it means

Thrown by the 'messages/get' Meteor method (getMessageHistory in apps/meteor/server/publications/messages.ts) when cursor pagination parameters ('next' or 'previous') are combined with 'lastUpdate'. The API supports three mutually exclusive modes: plain channel history, lastUpdate-based sync, and cursor pagination. Mixing the sync mode with cursor pagination is rejected because the server would not know which query shape to build.

Source

Thrown at apps/meteor/server/publications/messages.ts:257

> => {
	if (!(await canAccessRoomIdAsync(rid, fromId))) {
		throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'messages/get' });
	}

	if (type && !['UPDATED', 'DELETED'].includes(type)) {
		throw new Meteor.Error('error-type-param-not-supported', 'The "type" parameter must be either "UPDATED" or "DELETED"');
	}

	if ((next || previous) && !type) {
		throw new Meteor.Error('error-type-param-required', 'The "type" parameter is required when using the "next" or "previous" parameters');
	}

	if (next && previous) {
		throw new Meteor.Error('error-cursor-conflict', 'You cannot provide both "next" and "previous" parameters');
	}

	if ((next || previous) && lastUpdate) {
		throw new Meteor.Error(
			'error-cursor-and-lastUpdate-conflict',
			'The attributes "next", "previous" and "lastUpdate" cannot be used together',
		);
	}

	// `fromTs` only bounds the query on the `lastUpdate` path; neither cursor pagination nor the channel
	// history fallback honors it, so accepting it there would silently widen the result set.
	if (fromTs && !lastUpdate) {
		throw new Meteor.Error('error-fromTs-requires-lastUpdate', 'The "fromTs" parameter can only be used together with "lastUpdate"');
	}

	const hasCursorPagination = !!((next || previous) && count !== null && type);

	if (!hasCursorPagination && !lastUpdate) {
		return getChannelHistory({ rid, fromUserId: fromId, latest: latestDate, oldest: oldestDate, inclusive, count, unreads });
	}

	if (lastUpdate) {

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Remove 'lastUpdate' from the payload when using 'next'/'previous' cursor pagination
  2. Remove 'next'/'previous' when you want the lastUpdate sync mode
  3. Audit shared request builders so stale fields from the other mode are not forwarded

Example fix

// before
Meteor.call('messages/get', rid, { next: cursor, type: 'UPDATED', lastUpdate, count: 50 });

// after (cursor pagination only)
Meteor.call('messages/get', rid, { next: cursor, type: 'UPDATED', count: 50 });
Defensive patterns

Strategy: validation

Validate before calling

function assertMessagesGetParams(p: { next?: string; previous?: string; lastUpdate?: Date; type?: string; fromTs?: Date }) {
  const hasCursor = Boolean(p.next || p.previous);
  if (hasCursor && p.lastUpdate) throw new Error('cursor params cannot be combined with lastUpdate');
  if (p.next && p.previous) throw new Error('pass only one of next/previous');
  if (p.fromTs && !p.lastUpdate) throw new Error('fromTs requires lastUpdate');
}

Type guard

type CursorParams = { next?: string; previous?: string; type: 'UPDATED' | 'DELETED'; lastUpdate?: undefined; fromTs?: undefined };
type SyncParams = { lastUpdate: Date; fromTs?: Date; next?: undefined; previous?: undefined; type?: undefined };
function isCursorParams(p: CursorParams | SyncParams): p is CursorParams {
  return Boolean((p.next || p.previous) && p.type);
}

Try / catch

try { await Meteor.callAsync('messages/get', rid, params); } catch (e) { if (e.error === 'error-cursor-and-lastUpdate-conflict') { /* strip lastUpdate and retry in cursor mode */ } }

Prevention

When it happens

Trigger: Calling Meteor.call('messages/get', rid, { next: '<cursor>', type: 'UPDATED', lastUpdate: new Date() }) — the same payload with 'previous' instead of 'next' triggers it as well. Also triggered when a client migrates from the old lastUpdate sync loop to cursor pagination but forgets to strip the old parameter.

Common situations: Upgrading a chat client to cursor-based history loading while keeping the legacy lastUpdate refresh payload; copy-pasting a sync payload and adding a cursor from a previous response; REST/g RPC wrappers that forward all query params verbatim.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/16ae39dffce5579f. Report an issue: GitHub.