RocketChat/Rocket.Chat · error · Meteor.Error

error-token-does-not-exists

error-token-does-not-exists

Error message

Token does not exist

What it means

Thrown by removePersonalAccessTokenOfUser when Users.findPersonalAccessTokenByTokenNameAndUserId returns null — there is no token with that name to remove. The deletion is a no-op only at the DB level; the application treats a missing token as an error so callers know the name was wrong.

Source

Thrown at apps/meteor/imports/personal-access-tokens/server/api/methods/removeToken.ts:25

declare module '@rocket.chat/ddp-client' {
	// eslint-disable-next-line @typescript-eslint/naming-convention
	interface ServerMethods {
		'personalAccessTokens:removeToken'(params: { tokenName: string }): Promise<void>;
	}
}

export const removePersonalAccessTokenOfUser = async (tokenName: string, userId: string): Promise<void> => {
	if (!(await hasPermissionAsync(userId, 'create-personal-access-tokens'))) {
		throw new Meteor.Error('not-authorized', 'Not Authorized', {
			method: 'personalAccessTokens:removeToken',
		});
	}
	const tokenExist = await Users.findPersonalAccessTokenByTokenNameAndUserId({
		userId,
		tokenName,
	});
	if (!tokenExist) {
		throw new Meteor.Error('error-token-does-not-exists', 'Token does not exist', {
			method: 'personalAccessTokens:removeToken',
		});
	}
	await Users.removePersonalAccessTokenOfUser({
		userId,
		loginTokenObject: {
			type: 'personalAccessToken',
			name: tokenName,
		},
	});
};

Meteor.methods<ServerMethods>({
	'personalAccessTokens:removeToken': twoFactorRequired(async function ({ tokenName }: { tokenName: string }) {
		const uid = Meteor.userId();
		if (!uid) {
			throw new Meteor.Error('not-authorized', 'Not Authorized', {
				method: 'personalAccessTokens:removeToken',

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Treat the error as success if the goal is simply 'token must not exist' (idempotent remove).
  2. Refresh the token list before allowing a delete action.
  3. Verify the exact tokenName spelling and case.
  4. On the UI, hide the deleted token row immediately to prevent double submits.

Example fix

// before
await removePersonalAccessTokenOfUser(tokenName, userId); // throws if already gone

// after
try {
  await removePersonalAccessTokenOfUser(tokenName, userId);
} catch (e) {
  if (e.error !== 'error-token-does-not-exists') throw e;
  // already removed — treat as success
}
Defensive patterns

Strategy: try-catch

Validate before calling

async function tokenExists(userId: string, tokenName: string): Promise<boolean> {
  const t = await Users.findPersonalAccessTokenByTokenNameAndUserId({ userId, tokenName });
  return Boolean(t);
}

Try / catch

try {
  await removePersonalAccessTokenOfUser(tokenName, userId);
} catch (e) {
  if (e.error === 'error-token-does-not-exists') {
    // treat as success — token is already gone (idempotent remove)
  } else throw e;
}

Prevention

When it happens

Trigger: Calling removeToken with a tokenName that was already deleted, never existed, or is misspelled; double-submit of a remove action.

Common situations: User clicks delete twice; another session already removed it; stale UI list after deletion; typo in the name.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12). Data as JSON: /api/errors/4cd06381d9ef462f. Report an issue: GitHub.