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
- Remove 'lastUpdate' from the payload when using 'next'/'previous' cursor pagination
- Remove 'next'/'previous' when you want the lastUpdate sync mode
- 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
- Model the three request modes as discriminated unions so the compiler blocks mixed payloads
- Build the payload in exactly one place per mode instead of forwarding every field
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
- error-fromTs-requires-lastUpdate
- error-param-required
- error-invalid-user
- error-invalid-room
- error-invalid-command
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/16ae39dffce5579f.
Report an issue: GitHub.