instructure/canvas-lms · error · ImportError
A course did not pass validation
Error message
A course did not pass validation (course: #{course_id} / #{short_name}, error: #{course.errors.full_messages.join(",")}) What it means
SisCourseImporter#add_course raises ImportError when the course record itself fails ActiveRecord validation (course.valid? false) just before save_without_broadcasting!. This is the non-templated counterpart of the templated-course validation error; the embedded course.errors.full_messages list names the specific failing validations.
Solutions
- Parse the embedded errors.full_messages to find the exact failing attribute(s)
- Correct the offending values in courses.csv (e.g. ensure end_date >= start_date, names within limits) and re-run the batch
- Verify the course's term and account associations are valid and present under the root account
- Check for concurrent UI modifications to the course that make SIS-applied values invalid; reconcile before re-import
Example fix
// before: errors include "End date must be after the start date"
row = {start_date: "2026-05-31", end_date: "2026-01-01"}
// after
row = {start_date: "2026-01-01", end_date: "2026-05-31"} Defensive patterns
Strategy: try-catch
Validate before calling
msg = e.message
if (errs = msg[/error: (.*)\)$/, 1])
log.error("course validation failed: #{errs}")
end Type guard
nil
Try / catch
begin
importer.add_course(...)
rescue SisImport::ImportError => e
raise unless e.message.start_with?('A course did not pass validation')
sis_batch_errors.record(course_id, e.message) # includes errors.full_messages detail
end Prevention
- Validate dates in the export (end_date >= start_date, parseable formats)
- Enforce name/short_name length limits in the generator
- Capture and review the embedded errors.full_messages after every failed batch
- Reconcile concurrent UI edits before running SIS updates on the same courses
When it happens
Trigger: add_course reaches the final save path and course.valid? fails — e.g. invalid start/end dates (end before start), name too long, enrollment term restrictions, account_id nil, or uniqueness/constraint issues surfaced as validation errors.
Common situations: CSV rows with malformed or reversed start/end dates; names exceeding length limits after export transformations; conflicting concurrent edits in the UI leaving the course in a state SIS changes cannot validate against; term or account references failing association validations.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- A # user did not pass validation (user: # , # : # , error…
- A # did not pass validation (# : # , error: # )
- An institutional tag category did not pass validation
- A (templated) course did not pass validation
- account.errors.first.message
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/6d220066f9390f6c.
Report an issue: GitHub.
Appendix: source
Thrown at lib/sis/course_importer.rb:257
else
msg = "A (templated) course did not pass validation " \
"(course: #{course_id} / #{short_name}, error: " \
"#{templated_course.errors.full_messages.join(",")})"
raise ImportError, msg
end
end
course.sis_batch_id = @batch.id
if course.valid?
course_changes = course.changes
course.save_without_broadcasting!
auditor_state_changes(course, state_changes, course_changes)
data = SisBatchRollBackData.build_data(sis_batch: @batch, context: course)
@roll_back_data << data if data
else
msg = "A course did not pass validation " \
"(course: #{course_id} / #{short_name}, error: " \
"#{course.errors.full_messages.join(",")})"
raise ImportError, msg
end
@course_ids_to_update_associations.add(course.id) if update_account_associations
else
@courses_to_update_sis_batch_id << course.id
end
if blueprint_course_id && !course.deleted?
case blueprint_course_id
when "dissociate"
MasterCourses::ChildSubscription.active.find_by(child_course_id: course.id)&.destroy
else
@blueprint_associations[blueprint_course_id] ||= []
@blueprint_associations[blueprint_course_id] << course_id
end
end
enrollment_data = course.update_enrolled_users(sis_batch: @batch) if update_enrollments
course.update_enrollment_states_if_necessaryView on GitHub (pinned to 1c9f0bb801)