instructure/canvas-lms · error · ImportError
A (templated) course did not pass validation
Error message
A (templated) course did not pass validation (course: #{course_id} / #{short_name}, error: #{templated_course.errors.full_messages.join(",")}) What it means
SisCourseImporter#add_course raises ImportError when a templated (blueprint/linked) course fails ActiveRecord validation before save_without_broadcasting!. The message includes course_id, short_name, and the concatenated validation errors from templated_course.errors.full_messages, so the real cause is in the embedded error list.
Solutions
- Read the embedded errors.full_messages in the message to identify the exact failed validation
- Fix the offending course attribute in courses.csv (name length, dates, format) and re-import
- Check whether the blueprint/master course was altered in the UI in a way that now blocks SIS updates; reconcile it first
- Update Canvas if a model validation regression is rejecting previously valid SIS data
Example fix
// before: message shows errors like "Name is too long" for a 300-char name short_name = very_long_exported_code // after short_name = very_long_exported_code[0, 255]
Defensive patterns
Strategy: try-catch
Validate before calling
msg = e.message
if (errs = msg[/error: (.*)\)$/, 1])
log.error("templated 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 (templated) course did not pass validation')
sis_batch_errors.record(course_id, e.message) # surface embedded errors.full_messages to data owners
end Prevention
- Parse and forward the embedded errors.full_messages to whoever owns the data
- Keep blueprint/master courses consistent with SIS-managed attributes
- Respect name/field length limits when generating CSV values
- Reconcile UI-side blueprint edits with SIS data before large batches
When it happens
Trigger: add_course path where the course is a blueprint/templated course and templated_course.valid? fails after applying SIS changes (e.g. invalid name length, invalid dates, constraint violations on the child/templated course object).
Common situations: Blueprint course structures where the master course was modified outside SIS making child updates invalid; name/title exceeding column or validation limits; changes conflicting with master-course restrictions; data from the CSV violating model validations only visible on the templated copy.
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 course did not pass validation
- A # user did not pass validation (user: # , # : # , error…
- A # did not pass validation (# : # , error: # )
- An institutional tag category did not pass validation
- Improper status "# " for institutional tag category #
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/e9c4321e2f2e2384.
Report an issue: GitHub.
Appendix: source
Thrown at lib/sis/course_importer.rb:243
templated_course.name = course.name if !templated_course.stuck_sis_fields.include?(:name) && !course_name_stuck
templated_course.course_code = course.course_code if !templated_course.stuck_sis_fields.include?(:course_code) && !course_course_code_stuck
templated_course.enrollment_term = course.enrollment_term if !templated_course.stuck_sis_fields.include?(:enrollment_term_id) && !course_enrollment_term_id_stuck
if !templated_course.stuck_sis_fields.intersect?(%i[start_at conclude_at restrict_enrollments_to_course_dates]) && !course_dates_stuck
templated_course.start_at = course.start_at
templated_course.conclude_at = course.conclude_at
templated_course.restrict_enrollments_to_course_dates = course.restrict_enrollments_to_course_dates
end
templated_course.sis_batch_id = @batch.id
@course_ids_to_update_associations.add(templated_course.id) if templated_course.account_id_changed? || templated_course.root_account_id_changed?
if templated_course.valid?
changes = templated_course.changes
templated_course.save_without_broadcasting!
Auditors::Course.record_updated(templated_course, @batch_user, changes, source: :sis, sis_batch_id: @batch_id)
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.idView on GitHub (pinned to 1c9f0bb801)