instructure/canvas-lms · error · ImportError
Section # references course # which doesn't exist
Error message
Section #{section_id} references course #{course_id} which doesn't exist What it means
Referential-integrity error in SectionImporter::Work#add_section raised when the course_id supplied for the section passes the blank check but no matching course exists (or was not yet imported) in the root account. The section cannot be attached, so ingestion of the row fails.
Solutions
- Import/verify the course before its sections in the SIS run
- Confirm the course_id matches the course's sis_source_id
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at lib/sis/section_importer.rb:88 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/62228c436a8c6646.
Report an issue: GitHub.
Appendix: source
Thrown at lib/sis/section_importer.rb:88
@batch = batch
@root_account = root_account
@logger = logger
@success_count = 0
@sections_to_update_sis_batch_ids = []
@roll_back_data = []
@course_ids_to_update_associations = Set.new
@deleted_section_ids = Set.new
end
def add_section(section_id, course_id, name, status, start_date = nil, end_date = nil, integration_id = nil)
raise ImportError, "No section_id given for a section in course #{course_id}" if section_id.blank?
raise ImportError, "No course_id given for a section #{section_id}" if course_id.blank?
raise ImportError, "No name given for section #{section_id} in course #{course_id}" if name.blank? && status =~ /\Aactive/i
raise ImportError, "Improper status \"#{status}\" for section #{section_id} in course #{course_id}" unless /\Aactive|\Adeleted/i.match?(status)
return if @batch.skip_deletes? && status =~ /deleted/i
course = @root_account.all_courses.find_by(sis_source_id: course_id)
raise ImportError, "Section #{section_id} references course #{course_id} which doesn't exist" unless course
section = @root_account.course_sections.find_by(sis_source_id: section_id)
section ||= course.course_sections.where(sis_source_id: section_id).first_or_initialize
section.root_account = @root_account
# this is an easy way to load up the cache with data we already have
section.course = course if course.id == section.course_id
# only update the name on new records, and ones that haven't been changed since the last sis import
raise ImportError, "No name given for section #{section_id} in course #{course_id}" if name.blank? && section.new_record?
section.name = name if section.new_record? || (!section.stuck_sis_fields.include?(:name) && name.present?)
# update the course id if necessary
if section.course_id != course.id
if section.nonxlist_course_id
# this section is crosslisted
if (section.nonxlist_course_id != course.id && !section.stuck_sis_fields.include?(:course_id)) || (section.course.workflow_state == "deleted" && status.start_with?("active"))
# but the course id we were given didn't match the crosslist infoView on GitHub (pinned to 1c9f0bb801)