RocketChat/Rocket.Chat · error · Error
error-cursor-conflict
error-cursor-conflict
Error message
error-cursor-conflict
What it means
loadRoomHistory throws error-cursor-conflict when both next and previous boolean flags are set to true simultaneously. The API supports paginating in one direction at a time; next fetches newer messages and previous fetches older messages relative to the latest/oldest cursor. Requesting both directions at once is ambiguous and rejected.
Source
Thrown at apps/meteor/server/lib/messages/loadRoomHistory.ts:66
export async function loadRoomHistory({
userId,
next,
previous,
lastSeen,
count = 20,
showThreadMessages = true,
room,
}: {
userId?: string;
next?: string;
previous?: string;
lastSeen?: Date;
count?: number;
showThreadMessages?: boolean;
room: IRoom;
}): Promise<RoomHistoryResult> {
if (next && previous) {
throw new Error('error-cursor-conflict');
}
const rid = room._id;
const hiddenMessageTypes = getHiddenSystemMessages(room, settings.get<MessageTypesValues[]>('Hide_System_Messages'));
// One extra document reveals whether a further page exists.
const options: FindOptions<IMessage> = { sort: { ts: next ? 1 : -1 }, limit: count + 1 };
const records = next
? await Messages.findVisibleByRoomIdBetweenTimestampsNotContainingTypes(
rid,
decodeHistoryCursor(next),
FAR_FUTURE,
hiddenMessageTypes,
options,
showThreadMessages,
).toArray()View on GitHub (pinned to b263243745)
Solutions
- Set only one of next or previous to true per call
- Refactor the caller to derive next/previous from a single direction variable, e.g. next: dir === 'next', previous: dir === 'previous'
- Audit option-merging code (object spreads, defaults) so a true flag isn't carried over from a previous request
- If you need both newer and older messages, issue two separate calls
Example fix
// before
loadRoomHistory({ room, latest, next: true, previous: true });
// after
const goingNewer = direction === 'next';
loadRoomHistory({ room, latest, next: goingNewer, previous: !goingNewer }); Defensive patterns
Strategy: type-guard
Validate before calling
if (params.next && params.previous) {
throw new TypeError('pass next or previous, not both');
}
const { next = false, previous = false } = pickOne(params); Type guard
const isSingleDirection = (p: { next?: boolean; previous?: boolean }): boolean => !(p.next && p.previous); Try / catch
try {
await loadRoomHistory(args);
} catch (err) {
if (err instanceof Error && err.message === 'error-cursor-conflict') {
// retry with only one direction
await loadRoomHistory({ ...args, previous: false });
} else throw err;
} Prevention
- Derive next/previous from a single direction variable
- Avoid blind spreads of previous pagination options into new requests
- Disable the opposing direction control in the UI when one is selected
When it happens
Trigger: Calling loadRoomHistory with { latest, next: true, previous: true } (or the REST equivalent with both direction params truthy), typically when a UI toggles both direction switches or when defaults merge with explicit params.
Common situations: Spread-merging pagination option objects where a stale next:true combines with a new previous:true; UI code that sets both flags 'to be safe'; refactoring a component that previously used a single direction enum into two booleans.
Related errors
- error-invalid-cursor
- error-invalid-sort
- duplicated-account
- error-invalid-account
- error-user-registration-custom-field
AI-assisted analysis of RocketChat/Rocket.Chat@b263243745 (2026-08-28).
Data as JSON: /api/errors/0bd5f8b38ea5921e.
Report an issue: GitHub.