RocketChat/Rocket.Chat · error · Meteor.Error
error-not-allowed
error-not-allowed
Error message
Threads Disabled
What it means
Thrown by the POST chat.readThread REST endpoint (apps/meteor/server/api/v1/chat.ts:282) when the workspace setting 'Threads_enabled' is false. It is the very first check in the handler, so it fires before the tmid, room, or permission checks. Rocket.Chat exposes it as an error-not-allowed response, meaning the feature is administratively disabled rather than the caller doing something wrong.
Solutions
- Ask a workspace admin to enable threads: Administration > Workspace > Settings > Threads > Enable Threads (sets Threads_enabled = true), then retry the call.
- If you own the deployment, set the setting programmatically: POST /api/v1/settings/Threads_enabled {"value": true} with an admin token, then retry.
- If threads must stay disabled, stop calling chat.readThread and mark threads as read is not possible; remove the thread-read flow from your integration.
- Feature-detect before calling (GET /api/v1/settings.public or cached client config) and hide/disable the thread UI path when Threads_enabled is false.
Example fix
// before
await rc.post('/api/v1/chat.readThread', { tmid });
// throws { errorType: 'error-not-allowed', error: 'Threads Disabled' }
// after - only call when threads are enabled, catch as fallback
const { threadsEnabled } = await rc.get('/api/v1/settings.public?query={"Threads_enabled":1}');
if (threadsEnabled?.['Threads_enabled'] !== false) {
await rc.post('/api/v1/chat.readThread', { tmid });
} Defensive patterns
Strategy: try-catch
Validate before calling
const {
settings: { threadsEnabled },
} = await rc.get('/api/v1/settings.public');
if (!threadsEnabled) skipThreadFeatures(); Try / catch
try {
await rc.post('/api/v1/chat.readThread', { tmid });
} catch (e) {
if (e?.errorType === 'error-not-allowed' && /Threads Disabled/i.test(e.error ?? '')) {
markThreadsUnavailable(); // hide thread UI, do not retry
} else throw e;
} Prevention
- Read Threads_enabled once at bootstrap and gate every thread endpoint call behind it.
- Treat error-not-allowed 'Threads Disabled' as a sticky state - cache it for the session instead of re-probing per request.
When it happens
Trigger: POST /api/v1/chat.readThread with body {"tmid": "<thread main message id>"} against a workspace where Administration > Workspace > Settings > Threads is disabled (Threads_enabled = false). Any authenticated user, even a workspace admin issuing the call, gets this error until the setting is turned on.
Common situations: Workspaces that turned threads off for compliance or UX reasons; fresh installs where an admin disabled threads before rolling out to users; integrations/bots written against a threads-enabled workspace later pointed at one where threads were switched off; apps assuming threads are always available.
Related errors
- error-invalid-message
- 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/6633d66ebba9f1b8.
Report an issue: GitHub.
Appendix: source
Thrown at apps/meteor/server/api/v1/chat.ts:286
response: {
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
200: ajv.compile<void>({
type: 'object',
properties: {
success: {
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');
}
View on GitHub (pinned to e4b8178b20)