{"record":{"id":"16ae39dffce5579f","repo":"RocketChat/Rocket.Chat","slug":"error-cursor-and-lastupdate-conflict","errorCode":"error-cursor-and-lastUpdate-conflict","errorMessage":"The attributes \"next\", \"previous\" and \"lastUpdate\" cannot be used together","messagePattern":"The attributes \"next\", \"previous\" and \"lastUpdate\" cannot be used together","errorType":"error_code","errorClass":"Meteor.Error","httpStatus":400,"severity":"error","filePath":"apps/meteor/server/publications/messages.ts","lineNumber":257,"sourceCode":"> => {\n\tif (!(await canAccessRoomIdAsync(rid, fromId))) {\n\t\tthrow new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'messages/get' });\n\t}\n\n\tif (type && !['UPDATED', 'DELETED'].includes(type)) {\n\t\tthrow new Meteor.Error('error-type-param-not-supported', 'The \"type\" parameter must be either \"UPDATED\" or \"DELETED\"');\n\t}\n\n\tif ((next || previous) && !type) {\n\t\tthrow new Meteor.Error('error-type-param-required', 'The \"type\" parameter is required when using the \"next\" or \"previous\" parameters');\n\t}\n\n\tif (next && previous) {\n\t\tthrow new Meteor.Error('error-cursor-conflict', 'You cannot provide both \"next\" and \"previous\" parameters');\n\t}\n\n\tif ((next || previous) && lastUpdate) {\n\t\tthrow new Meteor.Error(\n\t\t\t'error-cursor-and-lastUpdate-conflict',\n\t\t\t'The attributes \"next\", \"previous\" and \"lastUpdate\" cannot be used together',\n\t\t);\n\t}\n\n\t// `fromTs` only bounds the query on the `lastUpdate` path; neither cursor pagination nor the channel\n\t// history fallback honors it, so accepting it there would silently widen the result set.\n\tif (fromTs && !lastUpdate) {\n\t\tthrow new Meteor.Error('error-fromTs-requires-lastUpdate', 'The \"fromTs\" parameter can only be used together with \"lastUpdate\"');\n\t}\n\n\tconst hasCursorPagination = !!((next || previous) && count !== null && type);\n\n\tif (!hasCursorPagination && !lastUpdate) {\n\t\treturn getChannelHistory({ rid, fromUserId: fromId, latest: latestDate, oldest: oldestDate, inclusive, count, unreads });\n\t}\n\n\tif (lastUpdate) {","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/publications/messages.ts#L239-L275","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nMeteor.call('messages/get', rid, { next: cursor, type: 'UPDATED', lastUpdate, count: 50 });\n\n// after (cursor pagination only)\nMeteor.call('messages/get', rid, { next: cursor, type: 'UPDATED', count: 50 });","handlingStrategy":"validation","validationCode":"function assertMessagesGetParams(p: { next?: string; previous?: string; lastUpdate?: Date; type?: string; fromTs?: Date }) {\n  const hasCursor = Boolean(p.next || p.previous);\n  if (hasCursor && p.lastUpdate) throw new Error('cursor params cannot be combined with lastUpdate');\n  if (p.next && p.previous) throw new Error('pass only one of next/previous');\n  if (p.fromTs && !p.lastUpdate) throw new Error('fromTs requires lastUpdate');\n}","typeGuard":"type CursorParams = { next?: string; previous?: string; type: 'UPDATED' | 'DELETED'; lastUpdate?: undefined; fromTs?: undefined };\ntype SyncParams = { lastUpdate: Date; fromTs?: Date; next?: undefined; previous?: undefined; type?: undefined };\nfunction isCursorParams(p: CursorParams | SyncParams): p is CursorParams {\n  return Boolean((p.next || p.previous) && p.type);\n}","tryCatchPattern":"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 */ } }","preventionTips":["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"],"tags":["meteor","messages-get","pagination","parameter-validation","rocket-chat"],"backgroundTag":"conflicting-query-parameters","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T08:17:14.275Z"}