RocketChat/Rocket.Chat · error · Meteor.Error
error-invalid-message
error-invalid-message
Error message
Invalid Message
What it means
Thrown by POST chat.readThread (apps/meteor/server/api/v1/chat.ts:289) when Messages.findOneById(tmid) returns null or a document without an rid. The endpoint needs the thread's parent message to resolve which room the thread lives in, so an unknown or malformed tmid fails here with error-invalid-message. Note the schema only requires tmid to be a non-empty string, so any well-formed but wrong id reaches this check.
Solutions
- Verify the id exists and is a thread parent first: GET /api/v1/chat.getMessage?msgId=<tmid> must return a message (and its rid) before you mark the thread read.
- Confirm you are passing the thread's main message id (the message other replies reference via tmid), not a reply's _id and not a room id.
- If the parent was deleted, drop the unread state locally - there is nothing to mark read.
- Add a 404-style branch in your client for error-invalid-message so stale ids are discarded instead of retried.
Example fix
// before
await rc.post('/api/v1/chat.readThread', { tmid: cachedTmid });
// error-invalid-message when the parent message is gone
// after
try {
await rc.get('/api/v1/chat.getMessage', { params: { msgId: cachedTmid } });
await rc.post('/api/v1/chat.readThread', { tmid: cachedTmid });
} catch (e) {
if (e?.errorType === 'error-invalid-message') forgetThread(cachedTmid);
} Defensive patterns
Strategy: validation
Validate before calling
const res = await rc.get('/api/v1/chat.getMessage', { params: { msgId: tmid } });
if (!res?.message?.rid) throw new Error('tmid is not a valid thread parent');
await rc.post('/api/v1/chat.readThread', { tmid }); Type guard
const isValidThreadParent = (m: { rid?: string } | null | undefined): m is { rid: string } =>
typeof m?.rid === 'string' && m.rid.length > 0; Try / catch
try {
await rc.post('/api/v1/chat.readThread', { tmid });
} catch (e) {
if (e?.errorType === 'error-invalid-message') discardThread(tmid); // gone, stop syncing
else throw e;
} Prevention
- Use ids only from live API responses in the same workspace; never persist them across re-imports.
- Pass the thread's parent message id (what replies point at via tmid), not a reply id or room id.
When it happens
Trigger: POST /api/v1/chat.readThread with a tmid that (a) does not exist in the Messages collection (typo, stale id from another workspace, deleted thread parent), (b) belongs to a message whose rid field is missing, or (c) was confused with a thread reply id or a room id. Threads must be enabled or you get error 320 first.
Common situations: Bots persisting message ids across workspace re-imports/migrations where ids change; clients resending tmid after the parent message was purged by a retention policy; passing msgId of a reply instead of the thread's main message; copy-paste truncation of the 17-char Mongo id.
Related errors
- error-not-allowed
- error-room-does-not-exist
- The required "mid" body param is missing.
- Cannot send system messages using 'chat.sendMessage'
- error-emoji-param-not-provided
AI-assisted analysis of RocketChat/Rocket.Chat@e4b8178b20 (2026-08-18).
Data as JSON: /api/errors/0c21c63e5b34771a.
Report an issue: GitHub.
Appendix: source
Thrown at apps/meteor/server/api/v1/chat.ts:293
type: 'boolean',
enum: [true],
},
},
required: ['success'],
additionalProperties: false,
}),
},
},
async function action() {
if (!settings.get<boolean>('Threads_enabled')) {
throw new Meteor.Error('error-not-allowed', 'Threads Disabled');
}
const { tmid } = this.bodyParams;
const thread = await Messages.findOneById(tmid, { projection: { rid: 1 } });
if (!thread?.rid) {
throw new Meteor.Error('error-invalid-message', 'Invalid Message');
}
const [user, room] = await Promise.all([
Users.findOneById(this.userId),
Rooms.findOneById(thread.rid, { projection: { ...roomAccessAttributes, t: 1, _id: 1 } }),
]);
if (!room) {
throw new Meteor.Error('error-room-does-not-exist', 'This room does not exist');
}
if (!user || !(await canAccessRoomAsync(room, user))) {
throw new Meteor.Error('error-not-allowed', 'Not Allowed');
}
await callbacks.run('beforeReadMessages', room._id, user._id);
await readThread({ user, room, tmid });
View on GitHub (pinned to e4b8178b20)