instructure/canvas-lms · error · ArgumentError

Cannot call with more than one user

Error message

Cannot call with more than one user

What it means

Enrollment.recompute_final_score_in_singleton schedules a singleton delayed job to recompute a final score for exactly one user in one course. The singleton key embeds user_id, so multiple users would collide; it raises ArgumentError if Array(user_id).size > 1.

Solutions

  1. Call the method once per user_id in a loop.
  2. Use Enrollment.recompute_final_score (plural) for batches.
  3. Assert Array(user_id).size == 1 at the call site before invoking.

Example fix

// before
Enrollment.recompute_final_score_in_singleton(user_ids, course_id)
// after
user_ids.each { |uid| Enrollment.recompute_final_score_in_singleton(uid, course_id) }
Defensive patterns

Strategy: validation

Validate before calling

raise "expected a single user id" unless Array(user_id).size == 1

Try / catch

begin
  Enrollment.recompute_final_score_in_singleton(user_id, course_id)
rescue ArgumentError
  user_ids.each { |uid| Enrollment.recompute_final_score_in_singleton(uid, course_id) }
end

Prevention

When it happens

Trigger: Calling Enrollment.recompute_final_score_in_singleton([1,2,3], course_id) or passing a collection/relation of users instead of a single id.

Common situations: Batch recomputation after grading changes: developer passes an array of student ids to the singleton variant instead of looping or using the plural method.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at app/models/enrollment.rb:1136

  # * An assignment is deleted/undeleted
  #
  # * An enrollment is accepted (to address the scenario where a student
  #   is transferred from one section to another, and final grades need
  #   to be transferred)
  #
  # If some new feature comes up that affects calculation of a user's score,
  # please add appropriate calls to this so that the cached values don't get
  # stale! And once you've added the call, add the condition to the comment
  # here for future enlightenment.

  def self.recompute_final_score(*, **)
    GradeCalculator.recompute_final_score(*, **)
  end

  # This method is intended to not duplicate work for a single user.
  def self.recompute_final_score_in_singleton(user_id, course_id, **opts)
    # Guard against getting more than one user_id
    raise ArgumentError, "Cannot call with more than one user" if Array(user_id).size > 1

    delay_if_production(singleton: "Enrollment.recompute_final_score:#{user_id}:#{course_id}:#{opts[:grading_period_id]}",
                        max_attempts: 10)
      .recompute_final_score(user_id, course_id, **opts)
  end

  def self.recompute_due_dates_and_scores(user_id)
    Course.where(id: StudentEnrollment.where(user_id:).distinct.select(:course_id)).each do |course|
      SubmissionLifecycleManager.recompute_users_for_course([user_id], course, nil, update_grades: true)
    end
  end

  def self.recompute_final_scores(user_id)
    StudentEnrollment.where(user_id:).distinct.pluck(:course_id).each do |course_id|
      recompute_final_score_in_singleton(user_id, course_id)
    end
  end

View on GitHub (pinned to 1c9f0bb801)