RocketChat/Rocket.Chat · error · Error

Invalid username

Error message

Invalid username

What it means

Thrown by AppMessageBridge.typing when scope is 'room' but username is falsy. Room-scoped typing notifications are broadcast under a username, so the bridge cannot emit the user-activity event without one and rejects the call. It is a precondition on the typing descriptor for room scope.

Source

Thrown at apps/meteor/app/apps/server/bridges/messages.ts:110

		// #TODO: #AppsEngineTypes - Remove explicit types and typecasts once the apps-engine definition/implementation mismatch is fixed.
		const msg: IMessage | undefined = await this.orch.getConverters()?.get('messages').convertAppMessage(message);
		const convertedMessage = msg as IMessage;

		const users = (await Subscriptions.findByRoomIdWhenUserIdExists(room.id, { projection: { 'u._id': 1 } }).toArray()).map((s) => s.u._id);

		await Users.findByIds(users, { projection: { _id: 1 } }).forEach(
			({ _id }: { _id: string }) =>
				void api.broadcast('notify.ephemeralMessage', _id, room.id, {
					...convertedMessage,
				}),
		);
	}

	protected async typing({ scope, id, username, isTyping }: ITypingDescriptor): Promise<void> {
		switch (scope) {
			case 'room':
				if (!username) {
					throw new Error('Invalid username');
				}

				notifications.notifyRoom(id, 'user-activity', username, isTyping ? ['user-typing'] : []);
				return;
			default:
				throw new Error('Unrecognized typing scope provided');
		}
	}

	private isValidReaction(reaction: Reaction): boolean {
		return reaction.startsWith(':') && reaction.endsWith(':');
	}

	protected async addReaction(messageId: string, userId: string, reaction: Reaction): Promise<void> {
		if (!this.isValidReaction(reaction)) {
			throw new Error('Invalid reaction');
		}

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Set username to the typing user's username whenever scope is 'room'.
  2. Resolve the user via the read accessor and use its username field.
  3. Add a guard: if (scope === 'room' && !username) fail fast with a clear message.
  4. Validate the ITypingDescriptor shape before calling the notifier.

Example fix

// before
await app.getModify().typing({ scope: 'room', id: room.id, username: undefined, isTyping: true });

// after
if (!user.username) {
  throw new Error('Cannot send typing indicator without a username');
}
await app.getModify().typing({ scope: 'room', id: room.id, username: user.username, isTyping: true });
Defensive patterns

Strategy: validation

Validate before calling

function assertRoomTypingDescriptor(descriptor: ITypingDescriptor): asserts descriptor is ITypingDescriptor & { username: string } {
  if (descriptor.scope === 'room' && !descriptor.username) {
    throw new Error('Room-scoped typing requires a username');
  }
}
assertRoomTypingDescriptor(descriptor);
await app.getModify().typing(descriptor);

Type guard

const isRoomTypingWithUsername = (d: ITypingDescriptor): d is ITypingDescriptor & { scope: 'room'; username: string } =>
  d.scope === 'room' && typeof d.username === 'string' && d.username.length > 0;

Try / catch

try {
  await app.getModify().typing(descriptor);
} catch (e) {
  if ((e as Error).message === 'Invalid username') {
    // resolve the user's username and retry
  }
  throw e;
}

Prevention

When it happens

Trigger: An App calls the typing notifier (e.g. app.getModify().typing(...)) with scope 'room' and username undefined/null/empty, e.g. the App did not set the typing user's username.

Common situations: App constructs an ITypingDescriptor without username; App reads the user from context where username is absent; App confuses userId with username; refactoring dropped the username field.

Related errors


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