{"record":{"id":"00d417014e1effeb","repo":"instructure/canvas-lms","slug":"can-t-add-participants-to-a-private-conversation","errorCode":null,"errorMessage":"can't add participants to a private conversation","messagePattern":"can't add participants to a private conversation","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/models/conversation.rb","lineNumber":172,"sourceCode":"      end\n      conversation\n    end\n  end\n\n  # conversations with more recipients than this should force individual messages\n  def self.max_group_conversation_size\n    Setting.get(\"max_group_conversation_size\", 100).to_i\n  end\n\n  def can_add_participants?(users)\n    (users.map(&:id) + conversation_participants.pluck(:user_id)).uniq.count <= self.class.max_group_conversation_size + 1\n  end\n\n  def add_participants(current_user, users, options = {})\n    message = nil\n    shard.activate do\n      user_ids = users.map(&:id).uniq\n      raise \"can't add participants to a private conversation\" if private?\n\n      transaction do\n        lock!\n        user_ids -= conversation_participants.map(&:user_id)\n        next if user_ids.empty?\n\n        if options[:no_messages]\n          bulk_insert_options = {\n            workflow_state: \"unread\",\n            message_count: 0,\n            private_hash:\n          }\n        else\n          last_message_at = conversation_messages.human.first.try(:created_at)\n          raise \"can't add participants if there are no messages\" unless last_message_at\n\n          num_messages = conversation_messages.human.size\n","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/conversation.rb#L154-L190","documentation":"Guard in Conversation#add_participants: attempting to add participants to a private (two-person) conversation, which Canvas doesn't support — private conversations cannot grow. Raised when users are added to a conversation whose privacy type is 'private'.","triggerScenarios":"Calling add_participants on a conversation with private? == true (created as a single-pair conversation) with a non-empty list of new users; bulk scripts iterating conversations without checking private?.","commonSituations":"UI or API attempting to add people to a 1:1 conversation; legacy conversations data with private_type set; automation that assumes all conversations are group conversations.","solutions":["Start a new group conversation that includes the original participants plus the new users instead of mutating the private one","Check conversation.private? before calling add_participants and skip/handle private ones","Convert workflows to use 'add to conversation' only for group conversations"],"exampleFix":"# before\nconversation.add_participants(user, new_users)\n# after\nunless conversation.private?\n  conversation.add_participants(user, new_users)\nelse\n  Conversation.initiate([*conversation.participants.map(&:id), *new_users.map(&:id)], false)\nend","handlingStrategy":"validation","validationCode":"raise 'cannot extend private conversation' if conversation.private?","typeGuard":"null","tryCatchPattern":"begin\n  conversation.add_participants(user, users)\nrescue RuntimeError => e\n  raise e unless e.message.include?('private conversation')\n  # initiate a new group conversation including everyone\nend","preventionTips":["Check private? before offering 'add people' UI","Initiate a fresh group conversation when the thread is private","Avoid blanket automation over mixed conversation types"],"tags":["ruby","conversations","private-conversation"],"backgroundTag":"unsupported-operation","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}