RocketChat/Rocket.Chat · error · Meteor.Error

error-cursor-conflict

error-cursor-conflict

Error message

You cannot provide both "next" and "previous" parameters

What it means

Cursor pagination in messages/get is one-directional per request: supplying both next and previous throws Meteor.Error 'error-cursor-conflict' before any data is fetched. Each response cursor pair indicates where to continue in each direction, but only one may be used per call.

Source

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

	  }
	| false
	| IMessage[]
	| { messages: IMessage[]; firstUnread?: any; unreadNotLoaded?: number }
> => {
	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) {

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Send only the direction you are paging: next to go forward, previous to go backward
  2. Keep a single active cursor in state, not both
  3. Treat a null cursor as 'no more pages in that direction' and omit it from the next request

Example fix

// before
Meteor.call('messages/get', rid, { type, next: cursor.next ?? undefined, previous: cursor.previous ?? undefined }); // both set -> error-cursor-conflict

// after
const paging = direction === 'older' ? { next: cursor.next ?? undefined } : { previous: cursor.previous ?? undefined };
Meteor.call('messages/get', rid, { type, ...paging });
Defensive patterns

Strategy: validation

Validate before calling

const hasSingleCursorDirection = (params: { next?: string; previous?: string }): boolean =>
  !(params.next && params.previous);

Type guard

const isSingleDirectionPaging = (p: unknown): p is { next?: string; previous?: string } => {
  const v = p as { next?: string; previous?: string };
  return !(v.next && v.previous);
};

Prevention

When it happens

Trigger: Calling messages/get with both next and previous set — most often a client echoing the whole response.cursor { next, previous } object verbatim into the next request.

Common situations: Mirroring the cursor object from a previous response; merging paging params from two branches of UI state (infinite scroll up + down); request builders that spread all stored params unconditionally.

Related errors


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