{"record":{"id":"bcdfb646c3486478","repo":"RocketChat/Rocket.Chat","slug":"error-tags-must-be-assigned-before-closing-chat","errorCode":"error-tags-must-be-assigned-before-closing-chat","errorMessage":"error-tags-must-be-assigned-before-closing-chat","messagePattern":"error-tags-must-be-assigned-before-closing-chat","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/lib/omnichannel/closeRoom.ts","lineNumber":274,"sourceCode":"\t\t};\n\t}\n\n\tconst { requestTagBeforeClosingChat, chatClosingTags } = department;\n\tconst extraRoomTags = concatUnique(roomTags, chatClosingTags);\n\n\tif (!requestTagBeforeClosingChat) {\n\t\treturn {\n\t\t\tupdatedOptions: {\n\t\t\t\t...options,\n\t\t\t\t...(extraRoomTags.length && { tags: extraRoomTags }),\n\t\t\t},\n\t\t};\n\t}\n\n\tconst checkRoomTags = !clientAction || (roomTags && roomTags.length > 0);\n\tconst checkDepartmentTags = chatClosingTags && chatClosingTags.length > 0;\n\tif (!checkRoomTags || !checkDepartmentTags) {\n\t\tthrow new Error('error-tags-must-be-assigned-before-closing-chat');\n\t}\n\n\treturn {\n\t\tupdatedOptions: {\n\t\t\t...options,\n\t\t\t...(extraRoomTags.length && { tags: extraRoomTags }),\n\t\t},\n\t};\n}\n\nexport async function closeOpenChats(userId: string, comment?: string) {\n\tlogger.debug({ msg: 'Closing open chats for user', userId });\n\tconst user = await Users.findOneById(userId);\n\n\tconst extraQuery = await applyDepartmentRestrictions({}, userId);\n\tconst openChats = LivechatRooms.findOpenByAgent(userId, extraQuery);\n\tconst promises: Promise<void>[] = [];\n\tawait openChats.forEach((room) => {","sourceCodeStart":256,"sourceCodeEnd":292,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/lib/omnichannel/closeRoom.ts#L256-L292","documentation":"resolveChatTags enforces the department-level tag policy before closing: if the room's department has requestTagBeforeClosingChat enabled, the close requires that (a) for client-initiated closes (clientAction) the room/options carry at least one tag, and (b) the department has chatClosingTags configured; otherwise Error('error-tags-must-be-assigned-before-closing-chat') is thrown. Rooms without a department, or whose department no longer exists, bypass the check entirely.","triggerScenarios":"Agent closes a chat in a department with requestTagBeforeClosingChat=true without selecting any tag; or the department has the policy on but chatClosingTags was never configured (fails even if the room already has tags); or a programmatic close with clientAction=true and no options.tags.","commonSituations":"Department admins enable the tag policy without configuring closing tags; custom clients and bulk close flows that never send tags; department configuration incompletely migrated between staging and production.","solutions":["Assign at least one tag to the chat before closing (tag picker in the UI, or options.tags on the close call)","If the department's chatClosingTags is empty, configure the allowed closing tags on the department","Disable requestTagBeforeClosingChat on the department when closing tags are not actually required","For server-initiated closes that must not require room tags, invoke the close without clientAction (the room-tag check is then waived, though chatClosingTags must still exist)"],"exampleFix":"// before - department requires tags -> throws\nawait closeRoom({ room, user, comment: 'done' });\n\n// after - supply the required tag\nawait closeRoom({ room, user, comment: 'done', options: { clientAction: true, tags: ['billing'] } });","handlingStrategy":"validation","validationCode":"import { LivechatDepartment } from '@rocket.chat/models';\n\n// before a client-initiated close:\nconst dept = room.departmentId\n  ? await LivechatDepartment.findOneById(room.departmentId, { projection: { requestTagBeforeClosingChat: 1, chatClosingTags: 1 } })\n  : null;\nif (dept?.requestTagBeforeClosingChat) {\n  const hasRoomTags = (room.tags?.length ?? 0) > 0 || (options.tags?.length ?? 0) > 0;\n  const hasDeptTags = (dept.chatClosingTags?.length ?? 0) > 0;\n  if ((options.clientAction && !hasRoomTags) || !hasDeptTags) {\n    // prompt for tags / configure chatClosingTags on the department before closing\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await closeRoom({ room, user, options: { clientAction: true, tags: ['billing'] } });\n} catch (err: any) {\n  if (err?.message === 'error-tags-must-be-assigned-before-closing-chat') return promptForTags(room);\n  throw err;\n}","preventionTips":["When enabling requestTagBeforeClosingChat on a department, configure chatClosingTags in the same change","Wire the close dialog to require a tag whenever the room's department policy is on","Automated close flows should pass tags in options, or run without clientAction where the policy permits"],"tags":["omnichannel","livechat","tags","department","policy"],"backgroundTag":"missing-required-tag","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}