instructure/canvas-lms · error · ConversationsHelper::Error
not a participant
Error message
not a participant
What it means
Rescue translator: when an included_message references a message whose author/owner is not a participant of the conversation (from the caller's perspective), ConversationsHelper::InvalidMessageParticipantError is re-raised as a bad_request ConversationsHelper::Error with 'not a participant'.
Solutions
- Ensure the current user is an active participant of the conversation before including its messages.
- Drop included_messages ids for conversations the user is not a participant of.
- Catch ConversationsHelper::Error (attribute included_messages, message 'not a participant') and show a friendly message.
- Re-open the conversation in the UI so the participant list refreshes, then retry.
Example fix
// before included_messages: Message.where(id: params[:included_messages]) // after included_messages: current_user.conversations.find(conversation.id) .conversation_messages.where(id: params[:included_messages]).pluck(:id)
Defensive patterns
Strategy: try-catch
Type guard
def participant?(conversation, user) conversation.conversation_participants.exists?(user_id: user.id) end
Try / catch
begin
add_message(...)
rescue ConversationsHelper::Error => e
if e.message == I18n.t('not a participant')
flash[:error] = t('you_are_no_longer_part_of_this_conversation')
end
end Prevention
- Verify current user is an active participant before including messages
- Drop cached message ids when the user leaves a conversation
- Re-fetch conversation state on UI resume
When it happens
Trigger: Conversations API call with included_messages pointing at a message the current user can access in another conversation context but who is not a conversation participant (e.g. observer/admin-visible message, or conversation the user was removed from).
Common situations: User removed from a group conversation still has cached message ids; forwarding a message received in a thread the user has since left; admin tools acting on behalf of a non-participant.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- not for this conversation
- can only forward one conversation at a time
- can't add participants if there are no messages
- can't add participants to a private conversation
- Course concluded, unable to send messages
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/3300540fc3d04d90.
Report an issue: GitHub.
Appendix: source
Thrown at app/helpers/conversations_helper.rb:144
message.conversation_id = conversation.conversation_id
message.created_at = Time.now.utc
{ message:, recipients_count: existing_participant_ids.size, status: :accepted }
end
elsif conversation.should_process_immediately?
message = conversation.process_new_message(message_args, recipients, message_ids, tags)
{ message:, recipients_count: recipients ? recipients.count : 0, status: :ok }
else
conversation.delay(strand: "add_message_#{conversation.global_conversation_id}").process_new_message(message_args, recipients, message_ids, tags)
message = Conversation.build_message(*message_args)
message.id = 0
message.conversation_id = conversation.conversation_id
message.created_at = Time.now.utc
{ message:, recipients_count: recipients ? recipients.count : 0, status: :accepted }
end
rescue ConversationsHelper::InvalidMessageForConversationError
raise ConversationsHelper::Error.new(message: I18n.t("not for this conversation"), status: :bad_request, attribute: "included_messages")
rescue ConversationsHelper::InvalidMessageParticipantError
raise ConversationsHelper::Error.new(message: I18n.t("not a participant"), status: :bad_request, attribute: "included_messages")
end
def contexts_for(audience, context_tags)
result = { courses: {}, groups: {} }
return result if audience.empty?
context_tags.each do |tag|
next unless tag =~ /\A(course|group)_(\d+)\z/
result[:"#{$1}s"][$2.to_i] = []
end
if audience.size == 1 && include_private_conversation_enrollments
enrollments = Shard.partition_by_shard(result[:courses].keys) do |course_ids|
next unless audience.first.associated_shards.include?(Shard.current)
Enrollment.where(course_id: course_ids, user_id: audience.first.id, workflow_state: "active").select([:course_id, :type]).to_a
endView on GitHub (pinned to 1c9f0bb801)