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

Peer review sub assignment does not exist

Error message

Peer review sub assignment does not exist

What it means

PeerReview::Validations#validate_peer_review_sub_assignment_exists raises PeerReview::SubAssignmentNotExistError when `assignment.peer_review_sub_assignment` is blank, i.e. the operation expects an existing peer review sub assignment (typically update/delete flows) but none has been created yet.

Solutions

  1. Create the sub assignment first via the creation service, then update it.
  2. Guard update/delete calls with `assignment.peer_review_sub_assignment.present?` and no-op (or create) when absent.
  3. Reconcile data: run the backfill that creates sub assignments for assignments with peer_reviews enabled.

Example fix

// before
PeerReview::SubAssignmentUpdater.new(assignment: assignment).call # no sub assignment yet
// after
if assignment.peer_review_sub_assignment.blank?
  PeerReview::SubAssignmentCreator.new(assignment: assignment).call
end
PeerReview::SubAssignmentUpdater.new(assignment: assignment).call
Defensive patterns

Strategy: validation

Validate before calling

return render json: { error: "no sub assignment" }, status: :not_found if assignment.peer_review_sub_assignment.blank?

Type guard

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

Try / catch

begin
  updater.call
rescue PeerReview::SubAssignmentNotExistError
  create_sub_assignment(assignment)
end

Prevention

When it happens

Trigger: Updating or deleting the peer review sub assignment of an assignment that never had one created; calling sync/update services before SubAssignmentCreator ran; the sub assignment having been deleted earlier.

Common situations: Race between sub assignment deletion and an update job; feature flag turned on later so legacy assignments have no sub assignment; re-running migrations/sync scripts on fresh assignments.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

  end

  def validate_grading_type(grading_type)
    raise PeerReview::InvalidGradingTypeError, I18n.t("Peer review sub assignments cannot have a not_graded grading type") if grading_type == "not_graded"
  end

  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?

View on GitHub (pinned to 1c9f0bb801)