instructure/canvas-lms · error · ImportError

No course_id given for a course

Error message

No course_id given for a course

What it means

SisCourseImporter#add_course raises ImportError when the course_id column value of a SIS courses.csv row is blank. Every course row in a SIS import must identify the course via course_id (the SIS source id) for matching, so an empty value cannot be processed. This is a row-level data validation guard at the very start of add_course, before any database work.

Solutions

  1. Fix the courses.csv row so course_id is populated with a non-blank unique SIS id, then re-run the import
  2. Verify CSV column ordering matches the expected courses.csv header so course_id is not parsed from the wrong column
  3. Filter or reject blank course_id rows upstream in the data export before feeding the SIS import
  4. Wrap the import batch in error handling that captures ImportError and reports the offending row for correction

Example fix

// before (courses.csv row)
,term-1,account-1,,active,2026-01-01,2026-05-31,,,,My Course
// after
course-101,term-1,account-1,,active,2026-01-01,2026-05-31,,,,My Course
Defensive patterns

Strategy: validation

Validate before calling

row = csv_row
raise ArgumentError, 'blank course_id' if row['course_id'].to_s.strip.empty?
importer.add_course(row['course_id'].strip, ...)

Type guard

def valid_course_id?(id) = id.is_a?(String) && !id.strip.empty?

Try / catch

begin
  importer.add_course(...)
rescue SisImport::ImportError => e
  raise if e.message != 'No course_id given for a course'
  log.warn("skipping course row with blank course_id")
end

Prevention

When it happens

Trigger: Calling SisCourseImporter#add_course with course_id=nil, "", or whitespace-only (Anything blank? -> true). Occurs when a courses.csv row has an empty course_id column, or API/XML SIS import input omits course_id.

Common situations: Hand-edited or exported courses.csv where the first column was accidentally deleted; CSV parsing misalignment shifting columns; template rows with only headers left in the file; automated data feeds exporting blank SIS ids for new records.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/604440afbc0e08ae. Report an issue: GitHub.

Appendix: source

Thrown at lib/sis/course_importer.rb:66

    class Work
      attr_accessor :success_count, :roll_back_data

      def initialize(batch, root_account, logger, a1, a2, m, batch_user, blueprint_associations)
        @batch = batch
        @batch_user = batch_user
        @root_account = root_account
        @courses_to_update_sis_batch_id = a1
        @course_ids_to_update_associations = a2
        @roll_back_data = []
        @blueprint_associations = blueprint_associations
        @messages = m
        @logger = logger
        @success_count = 0
      end

      def add_course(course_id, term_id, account_id, fallback_account_id, status, start_date, end_date, abstract_course_id, short_name, long_name, integration_id, course_format, blueprint_course_id, grade_passback_setting, homeroom_course, friendly_name)
        state_changes = []
        raise ImportError, "No course_id given for a course" if course_id.blank?
        raise ImportError, "No short_name given for course #{course_id}" if short_name.blank? && abstract_course_id.blank?
        raise ImportError, "No long_name given for course #{course_id}" if long_name.blank? && abstract_course_id.blank?
        raise ImportError, "Improper status \"#{status}\" for course #{course_id}" unless /\A(active|deleted|completed|unpublished|published)/i.match?(status)
        raise ImportError, "Invalid course_format \"#{course_format}\" for course #{course_id}" unless course_format.blank? || course_format =~ /\A(online|on_campus|blended|not_set)/i

        valid_grade_passback_settings = %w[nightly_sync disabled not_set]
        raise ImportError, "Invalid grade_passback_setting \"#{grade_passback_setting}\" for course #{course_id}" unless grade_passback_setting.blank? || valid_grade_passback_settings.include?(grade_passback_setting.downcase.strip)
        return if @batch.skip_deletes? && status =~ /deleted/i

        Course.unique_constraint_retry do
          course = @root_account.all_courses.find_by(sis_source_id: course_id)
          if course.nil?
            course = Course.new
            state_changes << :created
          else
            state_changes << :updated
          end
          course.saved_by = :sis_import

View on GitHub (pinned to 1c9f0bb801)