{"record":{"id":"ba0c33da4041cb0f","repo":"instructure/canvas-lms","slug":"course-concluded-unable-to-send-messages","errorCode":null,"errorMessage":"Course concluded, unable to send messages","messagePattern":"Course concluded, unable to send messages","errorType":"exception","errorClass":"ConversationsHelper::Error","httpStatus":401,"severity":"warning","filePath":"app/helpers/conversations_helper.rb","lineNumber":41,"sourceCode":"    context:,\n    current_user:,\n    session:,\n    recipients:,\n    context_code:,\n    message_ids:,\n    body:,\n    attachment_ids:,\n    domain_root_account_id:,\n    media_comment_id:,\n    media_comment_type:,\n    automated: false\n  )\n    if conversation.conversation.replies_locked_for?(current_user, recipients)\n      raise ConversationsHelper::RepliesLockedForUser.new(message: I18n.t(\"Unauthorized, unable to add messages to conversation\"), status: :unauthorized, attribute: \"workflow_state\")\n    end\n\n    if context.is_a?(Course) && context.workflow_state == \"completed\" && !context.grants_right?(current_user, session, :read_as_admin)\n      raise ConversationsHelper::Error.new(message: I18n.t(\"Course concluded, unable to send messages\"), status: :unauthorized, attribute: \"workflow_state\")\n    end\n\n    if body.blank?\n      raise ConversationsHelper::Error.new(message: I18n.t(\"Unable to create message without a body\"), status: :bad_request, attribute: \"empty_message\")\n    end\n\n    recipients = normalize_recipients(\n      recipients:,\n      context_code:,\n      conversation_id: conversation.conversation_id,\n      current_user:,\n      session:\n    )\n\n    if recipients && !conversation.conversation.can_add_participants?(recipients)\n      raise ConversationsHelper::Error.new(message: I18n.t(\"Too many participants for group conversation\"), status: :bad_request, attribute: \"recipients\")\n    end\n","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/helpers/conversations_helper.rb#L23-L59","documentation":"process_response rejects sending messages into a Course whose workflow_state is 'completed' unless the user has :read_as_admin rights, raising ConversationsHelper::Error with status :unauthorized. This enforces the Canvas rule that concluded courses no longer accept student conversation traffic.","triggerScenarios":"Calling process_response/trigger_out_of_office_auto_responses with context being a Course with workflow_state == 'completed' and a current_user lacking :read_as_admin (i.e. a student or non-admin participant).","commonSituations":"Sending conversation messages shortly after course conclusion; automated out-of-office replies processed after a course wrapped up; a user with a stale UI session replying post-conclusion.","solutions":["Check the course's workflow_state and grant rights before sending; skip or surface 'course concluded' in the UI","Rescue ConversationsHelper::Error with status :unauthorized in the caller and notify the user","Reopen the course (un-conclude) if messaging should continue","Perform the action as a user with :read_as_admin rights if appropriate"],"exampleFix":"// before\nprocess_response(...)\n// after\nif context.is_a?(Course) && context.workflow_state == 'completed' && !context.grants_right?(current_user, session, :read_as_admin)\n  render json: { error: 'course concluded' }, status: :unauthorized\nelse\n  process_response(...)\nend","handlingStrategy":"validation","validationCode":"if context.is_a?(Course) && context.workflow_state == 'completed' && !context.grants_right?(current_user, session, :read_as_admin)\n  return render json: { error: 'course concluded' }, status: :unauthorized\nend","typeGuard":null,"tryCatchPattern":"begin\n  process_response(...)\nrescue ConversationsHelper::Error => e\n  render json: { error: e.message }, status: e.status\nend","preventionTips":["Disable/annotate conversation UI for concluded courses","Re-check course state server-side on every send","Rescue ConversationsHelper::Error in automation paths"],"tags":["conversations","authorization","course-state"],"backgroundTag":"invalid-state-transition","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"}