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
- Make creation idempotent: check `assignment.peer_review_sub_assignment.present?` and return the existing sub assignment instead of creating a new one.
- Use the update service rather than the create service when a sub assignment already exists.
- 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
- Make creation idempotent (find-or-create)
- Deduplicate/lock background jobs per assignment ID
- Debounce double-submits in the frontend
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
- Due date cannot be before available from date
- Invalid parent assignment
- Must be a group assignment to create group overrides
- Peer review sub assignment does not exist
- Peer review sub assignments cannot have a not_graded…
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)