instructure/canvas-lms · error · PeerReview::InvalidDatesError
Available from date cannot be after until date
Error message
Available from date cannot be after until date
What it means
PeerReview::InvalidDatesError raised by validate_peer_review_dates when the peer review sub-assignment's unlock_at (available from) is later than its lock_at (until date). An availability window whose start exceeds its end is meaningless, so Canvas rejects it before persisting. Thrown whenever peer review dates are validated, both standalone and against the parent assignment.
Solutions
- Ensure unlock_at <= lock_at: raise lock_at or lower unlock_at in the payload
- If reviews should be open-ended, drop lock_at (nil) instead of leaving a stale early value
- If reviews should not be available yet, drop unlock_at or set it before lock_at
- Pre-validate: raise unless Time.parse(unlock_at) <= Time.parse(lock_at)
Example fix
// before
{ unlock_at: '2026-06-01T00:00:00Z', lock_at: '2026-05-01T00:00:00Z' }
// after
{ unlock_at: '2026-05-01T00:00:00Z', lock_at: '2026-06-01T00:00:00Z' } Defensive patterns
Strategy: validation
Validate before calling
def ordered_window?(dates) u = dates[:unlock_at]; l = dates[:lock_at] return true if u.nil? || l.nil? u = Time.zone.parse(u) if u.is_a?(String) l = Time.zone.parse(l) if l.is_a?(String) u <= l end
Type guard
def parse_iso_date(v) return v unless v.is_a?(String) raise ArgumentError, 'not ISO8601' unless Api::ISO8601_REGEX.match?(v) Time.zone.parse(v) end
Try / catch
begin
validator.validate_peer_review_dates(dates)
rescue PeerReview::InvalidDatesError => e
Rails.logger.warn("peer review date order rejected: #{e.message}")
flash[:error] = e.message
end Prevention
- Validate the full unlock <= due <= lock chain before any API call
- When updating one date, re-check it against the other two (partial updates break ordering)
- Beware timezone offsets: two 'same-day' dates in different zones can invert ordering
- Write a shared date-order assertion used by all peer review tooling
When it happens
Trigger: Submitting peer review dates where both unlock_at and lock_at are present and unlock_at > lock_at, e.g. unlock_at: '2026-06-01T00:00:00Z', lock_at: '2026-05-01T00:00:00Z', via the peer review sub assignment create/update API path.
Common situations: Bulk date-import scripts that set unlock_at to a new semester start but forget to push lock_at; UI edits that advance the available-from date without re-checking the until date; copying dates from another course with a later start.
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
- Due date cannot be after until date
- Invalid date format for
- Parent override due date cannot be before parent override…
- Peer review due date cannot be before assignment due date
- Peer review override available from date cannot be before…
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/6d21bc71dd0cc48f.
Report an issue: GitHub.
Appendix: source
Thrown at app/services/peer_review/validations.rb:99
else
parsed_dates[date_field.to_sym] = date_value
end
end
due_at = parsed_dates[:due_at]
unlock_at = parsed_dates[:unlock_at]
lock_at = parsed_dates[:lock_at]
if due_at && unlock_at && due_at < unlock_at
raise PeerReview::InvalidDatesError, I18n.t("Due date cannot be before available from date")
end
if due_at && lock_at && due_at > lock_at
raise PeerReview::InvalidDatesError, I18n.t("Due date cannot be after until date")
end
if unlock_at && lock_at && unlock_at > lock_at
raise PeerReview::InvalidDatesError, I18n.t("Available from date cannot be after until date")
end
end
# Validates that peer review override dates fall within parent assignment override dates
# assignment available from date <= assignment due date <= peer review available from date <= peer review due date <= peer review until date <= assignment until date
def validate_override_dates_against_parent_override(peer_review_override, parent_override)
parent_unlock_at = parent_override.unlock_at_overridden ? parent_override.unlock_at : nil
parent_due_at = parent_override.due_at_overridden ? parent_override.due_at : nil
parent_lock_at = parent_override.lock_at_overridden ? parent_override.lock_at : nil
validate_dates_within_parent_boundaries(
child_dates: peer_review_override,
parent_unlock_at:,
parent_due_at:,
parent_lock_at:,
is_override: true
)
endView on GitHub (pinned to 1c9f0bb801)