RocketChat/Rocket.Chat · error · Meteor.Error
error-not-allowed
error-not-allowed
Error message
not-allowed
What it means
unfollowMessage throws error-not-allowed immediately when mid is provided and the Threads_enabled setting is false. Unfollowing is a thread concept, so with threads disabled workspace-wide the operation is rejected before any message lookup happens.
Source
Thrown at apps/meteor/server/meteor-methods/messages/unfollowMessage.ts:24
import { Meteor } from 'meteor/meteor';
import { RateLimiterClass as RateLimiter } from '../../lib/RateLimiter';
import { canAccessRoomIdAsync } from '../../lib/authorization/canAccessRoom';
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
import { unfollow } from '../../lib/messaging/threads/functions';
import { notifyOnMessageChange } from '../../lib/notifyListener';
import { settings } from '../../settings';
declare module '@rocket.chat/ddp-client' {
// eslint-disable-next-line @typescript-eslint/naming-convention
interface ServerMethods {
unfollowMessage(message: { mid: IMessage['_id'] }): false | undefined;
}
}
export const unfollowMessage = async (user: IUser, { mid }: { mid: IMessage['_id'] }): Promise<false | undefined> => {
if (mid && !settings.get('Threads_enabled')) {
throw new Meteor.Error('error-not-allowed', 'not-allowed', { method: 'unfollowMessage' });
}
const message = await Messages.findOneById(mid);
if (!message) {
throw new Meteor.Error('error-invalid-message', 'Invalid message', {
method: 'unfollowMessage',
});
}
if (!(await canAccessRoomIdAsync(message.rid, user._id))) {
throw new Meteor.Error('error-not-allowed', 'not-allowed', { method: 'unfollowMessage' });
}
const id = message.tmid || message._id;
const unfollowResult = await unfollow({ rid: message.rid, tmid: id, uid: user._id });
void notifyOnMessageChange({View on GitHub (pinned to b2c16d5842)
Solutions
- Enable Threads_enabled or hide the unfollow UI when the setting is off
- Migrate to POST /v1/chat.unfollowMessage, the supported replacement surface
- Skip the call client-side when threads are disabled - it can never succeed
Defensive patterns
Strategy: validation
Validate before calling
// client: skip unfollow when threads are off
const threadsEnabled = settings.get('Threads_enabled');
if (threadsEnabled) {
await Meteor.callAsync('unfollowMessage', { mid });
} Try / catch
try {
await Meteor.callAsync('unfollowMessage', { mid });
} catch (e: any) {
if (e?.error === 'error-not-allowed') {
// threads disabled: clear the follow state locally, no retry
}
throw e;
} Prevention
- Hide follow/unfollow UI when Threads_enabled is false
- Migrate to POST /v1/chat.unfollowMessage (the method is deprecated in 9.0.0)
- Re-read the setting after workspace setting changes, not only at boot
When it happens
Trigger: Clicking 'unfollow' in a stale UI after threads were disabled; replaying an old client build against a workspace where threads got turned off; calling the deprecated method (9.0.0 -> POST /v1/chat.unfollowMessage) in tests without seeding the setting.
Common situations: Admin disabled threads while clients kept thread UI cached; automation written when threads were on, reused after they were turned off; CI without the setting enabled.
Related errors
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/6f41ccbfd2de0255.
Report an issue: GitHub.