instructure/canvas-lms · error · PeerReview::SubAssignmentExistsError

Peer review sub assignment exists

Error message

Peer review sub assignment exists

What it means

PeerReview::Validations#validate_peer_review_sub_assignment_not_exist raises PeerReview::SubAssignmentExistsError when `assignment.peer_review_sub_assignment` is present but the operation (creation flow) requires that no sub assignment exists yet. It enforces the one-sub-assignment-per-assignment invariant.

Solutions

  1. Make creation idempotent: check `assignment.peer_review_sub_assignment.present?` and return the existing sub assignment instead of creating a new one.
  2. Use the update service rather than the create service when a sub assignment already exists.
  3. Serialize/dedupe background jobs per assignment ID to avoid concurrent creation.

Example fix

// before
PeerReview::SubAssignmentCreator.new(assignment: assignment).call # runs even if one exists
// after
unless assignment.peer_review_sub_assignment.present?
  PeerReview::SubAssignmentCreator.new(assignment: assignment).call
end
Defensive patterns

Strategy: validation

Validate before calling

return assignment.peer_review_sub_assignment if assignment.peer_review_sub_assignment.present?

Type guard

def sub_assignment_exists?(assignment) = assignment.peer_review_sub_assignment.present?

Try / catch

begin
  creator.call
rescue PeerReview::SubAssignmentExistsError
  assignment.reload.peer_review_sub_assignment # already created; treat as success
end

Prevention

When it happens

Trigger: Calling the sub assignment creation service twice for the same parent assignment; a retry after a partially successful creation; syncing jobs creating sub assignments concurrently.

Common situations: Duplicate API calls from the frontend (double-click submit); background job retries without idempotency guards; course copy importing a sub assignment that already exists.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


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

Appendix: source

Thrown at app/services/peer_review/validations.rb:61

  def validate_assignment_submission_types(assignment)
    if assignment.external_tool?
      raise PeerReview::InvalidAssignmentSubmissionTypesError, I18n.t("Peer reviews cannot be used with External Tool assignments")
    end

    if assignment.submission_types == "discussion_topic"
      raise PeerReview::InvalidAssignmentSubmissionTypesError, I18n.t("Peer reviews cannot be used with Discussion Topic assignments")
    end
  end

  def validate_peer_review_sub_assignment_exists(assignment)
    if assignment.peer_review_sub_assignment.blank?
      raise PeerReview::SubAssignmentNotExistError, I18n.t("Peer review sub assignment does not exist")
    end
  end

  def validate_peer_review_sub_assignment_not_exist(assignment)
    if assignment.peer_review_sub_assignment.present?
      raise PeerReview::SubAssignmentExistsError, I18n.t("Peer review sub assignment exists")
    end
  end

  # Validates that peer review dates follow the restriction below
  # peer review unlock_at < peer review due_at <= peer review lock_at
  def validate_peer_review_dates(peer_review_dates)
    parsed_dates = {}

    %w[due_at unlock_at lock_at].each do |date_field|
      date_value = peer_review_dates.fetch(date_field.to_sym, nil)
      next unless date_value.present?

      # Accept both Time objects and ISO8601 strings for API compatibility
      if date_value.is_a?(String)
        unless Api::ISO8601_REGEX.match?(date_value)
          raise PeerReview::InvalidDatesError, I18n.t("Invalid datetime format for %{attribute}", attribute: date_field)
        end

View on GitHub (pinned to 1c9f0bb801)