instructure/canvas-lms · error

conversation_ids needs to be scoped to a user

Error message

conversation_ids needs to be scoped to a user

What it means

ConversationParticipant.conversation_ids is designed as a scope-chaining helper that requires the relation be scoped to a specific user_id. It introspects the relation's WHERE predicates and raises if none constrain user_id, because running it unscoped would leak conversation IDs across users.

Solutions

  1. Scope the relation to a user first: user.conversation_participants.conversation_ids or ConversationParticipant.where(user_id: user).conversation_ids
  2. Reorder scope chaining so where(user_id:) is applied before calling conversation_ids
  3. In the regex-compatible path, a raw SQL string predicate like 'user_id = 5' also satisfies the check

Example fix

// before
ConversationParticipant.conversation_ids
// after
ConversationParticipant.where(user_id: user).conversation_ids
Defensive patterns

Strategy: validation

Validate before calling

raise 'scope to a user first' unless relation.where_clause Predicates inspect — simply always build as ConversationParticipant.where(user_id: user).conversation_ids

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: Calling ConversationParticipant.conversation_ids on a relation without a where(user_id: ...) clause, e.g. ConversationParticipant.conversation_ids directly, or after chaining only non-user predicates (workflow_state, etc.).

Common situations: Refactoring code that dropped the user scope; building ad-hoc console/admin queries without scoping; composing scopes where the user_id condition is applied later than this call.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/3f33f5d197c1b04a. Report an issue: GitHub.

Appendix: source

Thrown at app/models/conversation_participant.rb:556

    @last_message ||= messages.human.first if last_message_at
  end

  attr_writer :last_authored_message

  def last_authored_message
    @last_authored_message ||= conversation.shard.activate { messages.human.by_user(user_id).first } if visible_last_authored_at
  end

  def self.preload_latest_messages(conversations, author)
    # preload last_message
    ConversationMessage.preload_latest conversations.select(&:last_message_at)
    # preload last_authored_message
    ConversationMessage.preload_latest conversations.select(&:visible_last_authored_at), author
  end

  def self.conversation_ids
    where_predicates = all.where_clause.instance_variable_get(:@predicates)
    raise "conversation_ids needs to be scoped to a user" unless where_predicates.any? do |v|
      if v.is_a?(Arel::Nodes::Binary) && v.left.is_a?(Arel::Attributes::Attribute)
        v.left.name == "user_id"
      else
        v =~ /user_id (?:= |IN \()\d+/
      end
    end

    order = "last_message_at DESC" unless all.order_values.present?
    self.order(order).pluck(:conversation_id)
  end

  def self.users_by_conversation_shard(user_ids)
    { Shard.current => user_ids }
  end

  def update_one(update_params)
    case update_params[:event]

View on GitHub (pinned to 1c9f0bb801)