RocketChat/Rocket.Chat · error · Meteor.Error
error-action-not-allowed
error-action-not-allowed
Error message
Editing settings is not allowed
What it means
The `saveSetting` Meteor method requires an authenticated DDP session: the very first check after the deprecation log is `Meteor.userId()`, and a null userId throws `error-action-not-allowed` before any permission or value validation. Note the method is wrapped in `twoFactorRequired` and is deprecated since 9.0.0 in favor of the REST endpoint `PUT /v1/settings/:_id`.
Solutions
- Guard the call: only invoke `saveSetting` after `Meteor.userId()` is non-null (wait for the login sequence).
- For programmatic access, use the REST endpoint `PUT /api/v1/settings/:_id` with an auth token instead of the deprecated DDP method.
- Re-authenticate (loginWithPassword/loginWithToken) on `connection rejected` / expired-token events before retrying.
- Check for a logged-out tab or a second connection with stale credentials when the error appears intermittently.
Example fix
// before
Meteor.call('saveSetting', _id, value, editor);
// after
if (!Meteor.userId()) {
// wait for login (or redirect to login) before saving settings
return;
}
Meteor.call('saveSetting', _id, value, editor); Defensive patterns
Strategy: validation
Validate before calling
if (!Meteor.userId()) {
// wait for the login chain; do not call saveSetting unauthenticated
return;
}
await Meteor.callAsync('saveSetting', _id, value, editor); Type guard
const isAuthenticated = (): boolean => typeof Meteor.userId() === 'string';
Try / catch
try {
await Meteor.callAsync('saveSetting', _id, value, editor);
} catch (e: any) {
if (e?.error === 'error-action-not-allowed' && !Meteor.userId()) {
// authentication issue, not permissions: re-authenticate and retry once
}
} Prevention
- Gate settings saves on a reactive loggedIn state.
- Prefer REST /api/v1/settings/:_id with tokens for programmatic access.
- Handle token-expiry reconnects by re-authenticating before retrying method calls.
When it happens
Trigger: Invoking `Meteor.call('saveSetting', _id, value, editor)` from an unauthenticated connection: before login completes on app boot, after logout, after the DDP resume token expired following a reconnect, or from server-side code that runs without a user context.
Common situations: Client code firing during initial page load before `Meteor.userId()` is set; long-lived sessions whose resume token was invalidated; scripts or integrations calling DDP methods without logging in first; migrations to the REST API missing this endpoint.
Related errors
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/8a17a94c73a98777.
Report an issue: GitHub.
Appendix: source
Thrown at apps/meteor/server/meteor-methods/settings/saveSetting.ts:28
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
import { notifyOnSettingChanged } from '../../lib/notifyListener';
import { SettingValidationError, validateSettingRules } from '../../lib/settingValidationRules';
import { disableCustomScripts } from '../../lib/shared/disableCustomScripts';
import { updateAuditedByUser } from '../../settings/lib/auditedSettingUpdates';
declare module '@rocket.chat/ddp-client' {
// eslint-disable-next-line @typescript-eslint/naming-convention
interface ServerMethods {
saveSetting(_id: string, value: SettingValue, editor: SettingEditor): Promise<boolean>;
}
}
Meteor.methods<ServerMethods>({
saveSetting: twoFactorRequired(async function (_id: string, value: SettingValue, editor: SettingEditor) {
methodDeprecationLogger.method('saveSetting', '9.0.0', '/v1/settings/:_id');
const uid = Meteor.userId();
if (!uid) {
throw new Meteor.Error('error-action-not-allowed', 'Editing settings is not allowed', {
method: 'saveSetting',
});
}
if (
!(await hasPermissionAsync(uid, 'edit-privileged-setting')) &&
!(await hasAllPermissionAsync(uid, ['manage-selected-settings', getSettingPermissionId(_id)]))
) {
// TODO use the same function
throw new Meteor.Error('error-action-not-allowed', 'Editing settings is not allowed', {
method: 'saveSetting',
settingId: _id,
});
}
// Verify the _id passed in is a string.
check(_id, String);
View on GitHub (pinned to b2c16d5842)