RocketChat/Rocket.Chat · error · Meteor.Error
error-not-allowed
error-not-allowed
Error message
Not allowed
What it means
After fetching the requested messages, getMessages maps every distinct room through canAccessRoomIdAsync; if ANY room is inaccessible it throws 'error-not-allowed' (note: the thrown details say method 'getSingleMessage' — a copy-paste artifact in the source). One inaccessible message rejects the whole batch, including all accessible messages.
Source
Thrown at apps/meteor/server/meteor-methods/messages/getMessages.ts:29
interface ServerMethods {
getMessages(messages: IMessage['_id'][]): Promise<IMessage[]>;
}
}
Meteor.methods<ServerMethods>({
async getMessages(messages) {
check(messages, [String]);
const uid = Meteor.userId();
if (!uid) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getMessages' });
}
const msgs = await Messages.findVisibleByIds(messages).toArray();
const rids = await Promise.all([...new Set(msgs.map((m) => m.rid))].map((_id) => canAccessRoomIdAsync(_id, uid)));
if (!rids.every(Boolean)) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'getSingleMessage' });
}
return msgs;
},
});
View on GitHub (pinned to b2c16d5842)
Solutions
- Pre-filter ids: only request messages the client knows belong to rooms the user is subscribed to
- Split the request into per-room or smaller batches so one bad id does not sink the rest
- Catch 'error-not-allowed' and degrade to per-message lookups (getSingleMessage), skipping the inaccessible ones
Example fix
// before
const msgs = await Meteor.callAsync('getMessages', ids);
// after — fall back to per-id fetch on batch rejection
try {
msgs = await Meteor.callAsync('getMessages', ids);
} catch (e) {
if (e.error !== 'error-not-allowed') throw e;
msgs = (await Promise.allSettled(
ids.map((id) => Meteor.callAsync('getSingleMessage', id)),
)).filter((r) => r.status === 'fulfilled').map((r) => r.value);
} Defensive patterns
Strategy: fallback
Try / catch
async function getMessagesSafe(ids: string[]) {
try {
return await Meteor.callAsync('getMessages', ids);
} catch (e) {
if ((e as Meteor.Error).error !== 'error-not-allowed') throw e;
// one bad room poisons the batch: degrade to per-id fetch, skip failures
const results = await Promise.allSettled(
ids.map((id) => Meteor.callAsync('getSingleMessage', id)),
);
return results
.filter((r): r is PromiseFulfilledResult<any> => r.status === 'fulfilled')
.map((r) => r.value)
.filter(Boolean);
}
} Prevention
- Batch only ids whose rooms the user is known to subscribe to
- Split heterogeneous id lists (search results, quotes) into per-room requests
- Treat batch 'error-not-allowed' as partial data, not a total failure
When it happens
Trigger: Meteor.call('getMessages', ids) where at least one id belongs to a private room or DM the caller cannot access — e.g. quoted messages from channels the user was removed from, or ids harvested from search results or links.
Common situations: Resolving quoted-message previews across mixed rooms; users removed from channels retaining old references; ids aggregated from multiple sources into one request.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/c505ff113de2c920.
Report an issue: GitHub.