RocketChat/Rocket.Chat · error · Meteor.Error

error-message-ts-out-of-sync

error-message-ts-out-of-sync

Error message

Message timestamp is out of sync

What it means

When no server-side ts override is supplied and the message includes a client timestamp, executeSendMessage compares it with server time. Differences over 10 seconds are silently replaced by 'now', but a difference beyond 60 seconds throws error-message-ts-out-of-sync (the payload carries message_ts and server_ts for diagnosis). The guard prevents backdated or future-dated messages.

Source

Thrown at apps/meteor/server/meteor-methods/messages/sendMessage.ts:55

	if (message.tshow && !message.tmid) {
		throw new Meteor.Error('invalid-params', 'tshow provided but missing tmid', {
			method: 'sendMessage',
		});
	}

	if (message.tmid && !settings.get('Threads_enabled')) {
		throw new Meteor.Error('error-not-allowed', 'not-allowed', {
			method: 'sendMessage',
		});
	}

	const isTimestampFromClient = Boolean(!extraInfo?.ts && message.ts);
	const now = new Date();
	message.ts = extraInfo?.ts ?? message.ts ?? now;
	if (isTimestampFromClient) {
		const tsDiff = Math.abs(moment(message.ts).diff(Date.now()));
		if (tsDiff > 60000) {
			throw new Meteor.Error('error-message-ts-out-of-sync', 'Message timestamp is out of sync', {
				method: 'sendMessage',
				message_ts: message.ts,
				server_ts: new Date().getTime(),
			});
		}
		if (tsDiff > 10000) {
			message.ts = now;
		}
	}

	if (message.msg) {
		if (message.msg.length > (settings.get<number>('Message_MaxAllowedSize') ?? 0)) {
			throw new Meteor.Error('error-message-size-exceeded', 'Message size exceeds Message_MaxAllowedSize', {
				method: 'sendMessage',
			});
		}
	}

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Omit ts from the message payload and let the server stamp it
  2. Sync the client clock / use the server time offset before sending
  3. For legitimate server-controlled timestamps (imports, migrations) call executeSendMessage server-side with extraInfo.ts, which bypasses the client-ts check

Example fix

// before: client-stamped ts, breaks when clock drifts
await Meteor.callAsync('sendMessage', { rid, msg, ts: new Date() });

// after: let the server set the timestamp
await Meteor.callAsync('sendMessage', { rid, msg });
Defensive patterns

Strategy: validation

Validate before calling

// client: never send a client-stamped ts; check drift if you must
const driftMs = Math.abs(Date.now() - serverNow);
if (driftMs > 60000) {
	await resyncClock();
}
await Meteor.callAsync('sendMessage', { rid, msg }); // no ts field

Try / catch

try {
	await Meteor.callAsync('sendMessage', message);
} catch (e: any) {
	if (e?.error === 'error-message-ts-out-of-sync') {
		// resend without message.ts and let the server stamp it
		const { ts, ...rest } = message;
		await Meteor.callAsync('sendMessage', rest);
		return;
	}
	throw e;
}

Prevention

When it happens

Trigger: Client clock skewed by minutes: wrong system time/timezone set as local time, VM paused and resumed, mobile device drift, NTP not running; explicitly setting message.ts to a past or future date; an offline queue replaying old messages with their original timestamps.

Common situations: Laptop clock off by minutes after travel or sleep; docker/dev VM without time sync; client-side offline-first queue that stamps messages locally and replays them later; deliberate attempts to forge message times.

Related errors


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