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
- Omit ts from the message payload and let the server stamp it
- Sync the client clock / use the server time offset before sending
- 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
- Omit ts on send; the server stamps messages authoritatively
- Use the server time offset for any client display that feeds back into payloads
- For imports, pass ts server-side via executeSendMessage's extraInfo.ts, which is exempt from the check
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.