{"record":{"id":"90a3e9c37fc77002","repo":"RocketChat/Rocket.Chat","slug":"firsterror-reason-error","errorCode":null,"errorMessage":"firstError.reason.error","messagePattern":"firstError\\.reason\\.error","errorType":"exception","errorClass":"Error","httpStatus":400,"severity":"error","filePath":"apps/meteor/server/api/v1/omnichannel/room.ts","lineNumber":467,"sourceCode":"\t\t\t}\n\n\t\t\tif (\n\t\t\t\t(!room.servedBy || room.servedBy._id !== this.userId) &&\n\t\t\t\t!(await hasPermissionAsync(this.user, 'save-others-livechat-room-info'))\n\t\t\t) {\n\t\t\t\treturn API.v1.forbidden();\n\t\t\t}\n\n\t\t\tif (room.sms) {\n\t\t\t\tdelete guestData.phone;\n\t\t\t}\n\n\t\t\t// We want this both operations to be concurrent, so we have to go with Promise.allSettled\n\t\t\tconst result = await Promise.allSettled([saveGuest(guestData, this.userId), saveRoomInfo(roomData)]);\n\n\t\t\tconst firstError = result.find((item) => item.status === 'rejected');\n\t\t\tif (firstError) {\n\t\t\t\tthrow new Error(firstError.reason.error);\n\t\t\t}\n\n\t\t\tawait callbacks.run('livechat.saveInfo', await LivechatRooms.findOneById(roomData._id), {\n\t\t\t\tuser: this.user,\n\t\t\t\toldRoom: room,\n\t\t\t});\n\n\t\t\treturn API.v1.success();\n\t\t},\n\t},\n);\n\nconst livechatRoomsEndpoints = API.v1\n\t.post(\n\t\t'livechat/rooms.delete',\n\t\t{\n\t\t\tresponse: {\n\t\t\t\t200: POSTLivechatRemoveRoomSuccess,","sourceCodeStart":449,"sourceCodeEnd":485,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/server/api/v1/omnichannel/room.ts#L449-L485","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect the actual message in the error response — it is the inner saveGuest/saveRoomInfo failure, not a generic code.","Validate guestData (email, phone, username uniqueness) and roomData (allowed fields) client-side before posting.","Reproduce with only guestData, then only roomData, to isolate which operation rejected.","Check server logs for the underlying rejection stack if reason.error is vague."],"exampleFix":"// before\nawait POST('/api/v1/livechat/room.saveInfo', { roomData, guestData });\n\n// after\ntry {\n  await POST('/api/v1/livechat/room.saveInfo', { roomData, guestData });\n} catch (e) {\n  // e.message is the inner saveGuest or saveRoomInfo rejection\n  if (/email/i.test(e.message)) warnGuestEmail();\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"const guestOk = validateGuestData(guestData); // email, phone, token uniqueness\nconst roomOk = validateRoomData(roomData); // allowed field names, types\nif (!guestOk || !roomOk) throw new ClientError('save-info-validation');","typeGuard":"null","tryCatchPattern":"try {\n  await POST('/api/v1/livechat/room.saveInfo', { roomData, guestData });\n} catch (e) {\n  // e.message is the inner saveGuest/saveRoomInfo rejection — branch on its text\n  if (/email|phone|token/i.test(e.message)) highlightGuestFields();\n  else highlightRoomFields();\n  throw e;\n}","preventionTips":["Validate guestData and roomData with the same schemas the server enforces before posting.","Test each half independently to localize failures.","Watch for schema changes in Rocket.Chat upgrades that tighten accepted fields."],"tags":["omnichannel","livechat","save-info","passthrough","data-integrity"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}