RocketChat/Rocket.Chat · error · Error

Invalid command parameter provided, must be a string.

Error message

Invalid command parameter provided, must be a string.

What it means

The authorize step hashes the submitted token with Accounts._hashLoginToken and searches users by services.resume.loginTokens.hashedToken. If no user matches, it responds 401 'Invalid token': the token was forged, truncated, revoked, or expired. Logging out deletes a user's resume tokens, so a token captured before logout becomes invalid immediately.

Solutions

  1. Log in again and resubmit the consent with the fresh resume token
  2. Make sure the token comes from the same environment/DB the OAuth server validates against
  3. Send the complete resume token string (no quotes/whitespace) as access_token or token
  4. If it persists, verify the user document still contains services.resume.loginTokens entries

Example fix

// before: stale token from an earlier session
body: new URLSearchParams({ allow: 'yes', access_token: oldToken });
// after: fetch a fresh resume token first
const { data } = await (await fetch('/api/v1/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user: 'me', password: '***' }) })).json();
body: new URLSearchParams({ allow: 'yes', access_token: data.authToken });
Defensive patterns

Strategy: validation

Validate before calling

const me = await fetch('/api/v1/me', { headers: { 'X-Auth-Token': token, 'X-User-Id': uid } });
if (!me.ok) { /* token invalid/expired: re-login before calling /oauth/authorize */ }

Try / catch

On 401 'Invalid token', discard the stored resume token, re-authenticate, then retry the authorize POST once with the fresh token — more than one retry indicates a deeper session problem.

Prevention

When it happens

Trigger: POST /oauth/authorize with allow=yes and a token that matches no services.resume.loginTokens entry: token from a user who has since logged out; token truncated or mangled in transit; token issued by a different deployment/database (dev vs prod); forged values.

Common situations: Consent submitted after the user logged out elsewhere; DB restored or reset so old tokens no longer exist; multi-replica setups where sessions are not shared; copy-paste errors in manual API testing.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/2c8337b54b22a69f. Report an issue: GitHub.

Appendix: source

Thrown at apps/meteor/app/apps/server/bridges/commands.ts:35

	}

	protected async doesCommandExist(command: string, appId: string): Promise<boolean> {
		this.orch.debugLog(`The App ${appId} is checking if "${command}" command exists.`);

		if (typeof command !== 'string' || command.length === 0) {
			return false;
		}

		const cmd = command.toLowerCase();

		return typeof slashCommands.commands[cmd] === 'object' || this.disabledCommands.has(cmd);
	}

	protected async enableCommand(command: string, appId: string): Promise<void> {
		this.orch.debugLog(`The App ${appId} is attempting to enable the command: "${command}"`);

		if (typeof command !== 'string' || command.trim().length === 0) {
			throw new Error('Invalid command parameter provided, must be a string.');
		}

		const cmd = command.toLowerCase();
		if (!this.disabledCommands.has(cmd)) {
			throw new Error(`The command is not currently disabled: "${cmd}"`);
		}

		slashCommands.commands[cmd] = this.disabledCommands.get(cmd) as (typeof slashCommands.commands)[string];
		this.disabledCommands.delete(cmd);

		void this.orch.getNotifier().commandUpdated(cmd);
	}

	protected async disableCommand(command: string, appId: string): Promise<void> {
		this.orch.debugLog(`The App ${appId} is attempting to disable the command: "${command}"`);

		if (typeof command !== 'string' || command.trim().length === 0) {
			throw new Error('Invalid command parameter provided, must be a string.');

View on GitHub (pinned to b2c16d5842)