RocketChat/Rocket.Chat · error · Error

firstError.reason.error

Error message

firstError.reason.error

What it means

Thrown by POST livechat/room.saveInfo when one of the two concurrent operations in Promise.allSettled([saveGuest, saveRoomInfo]) rejects. The code surfaces firstError.reason.error, i.e. the underlying rejection message from saveGuest or saveRoomInfo becomes the thrown error. This is a passthrough/re-throw of a deeper validation or persistence error.

Source

Thrown at apps/meteor/server/api/v1/omnichannel/room.ts:467

			}

			if (
				(!room.servedBy || room.servedBy._id !== this.userId) &&
				!(await hasPermissionAsync(this.user, 'save-others-livechat-room-info'))
			) {
				return API.v1.forbidden();
			}

			if (room.sms) {
				delete guestData.phone;
			}

			// We want this both operations to be concurrent, so we have to go with Promise.allSettled
			const result = await Promise.allSettled([saveGuest(guestData, this.userId), saveRoomInfo(roomData)]);

			const firstError = result.find((item) => item.status === 'rejected');
			if (firstError) {
				throw new Error(firstError.reason.error);
			}

			await callbacks.run('livechat.saveInfo', await LivechatRooms.findOneById(roomData._id), {
				user: this.user,
				oldRoom: room,
			});

			return API.v1.success();
		},
	},
);

const livechatRoomsEndpoints = API.v1
	.post(
		'livechat/rooms.delete',
		{
			response: {
				200: POSTLivechatRemoveRoomSuccess,

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Inspect the actual message in the error response — it is the inner saveGuest/saveRoomInfo failure, not a generic code.
  2. Validate guestData (email, phone, username uniqueness) and roomData (allowed fields) client-side before posting.
  3. Reproduce with only guestData, then only roomData, to isolate which operation rejected.
  4. Check server logs for the underlying rejection stack if reason.error is vague.

Example fix

// before
await POST('/api/v1/livechat/room.saveInfo', { roomData, guestData });

// after
try {
  await POST('/api/v1/livechat/room.saveInfo', { roomData, guestData });
} catch (e) {
  // e.message is the inner saveGuest or saveRoomInfo rejection
  if (/email/i.test(e.message)) warnGuestEmail();
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

const guestOk = validateGuestData(guestData); // email, phone, token uniqueness
const roomOk = validateRoomData(roomData); // allowed field names, types
if (!guestOk || !roomOk) throw new ClientError('save-info-validation');

Type guard

null

Try / catch

try {
  await POST('/api/v1/livechat/room.saveInfo', { roomData, guestData });
} catch (e) {
  // e.message is the inner saveGuest/saveRoomInfo rejection — branch on its text
  if (/email|phone|token/i.test(e.message)) highlightGuestFields();
  else highlightRoomFields();
  throw e;
}

Prevention

When it happens

Trigger: POST livechat/room.saveInfo where either saveGuest (guest field validation, duplicate token, invalid phone) or saveRoomInfo (room field validation, schema failure) throws. The surfaced message equals whatever the inner function threw as reason.error.

Common situations: guestData contains an invalid email/phone that fails guest schema validation; roomData has fields that violate the Livechat room schema; a duplicate custom field key; an AJV schema change in a newer Rocket.Chat version rejecting previously-accepted data; DB write error (duplicate key, connection drop) bubbling up as reason.error.

Related errors


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