instructure/canvas-lms · error · ImportError
Improper status "# " for course #
Error message
Improper status "#{status}" for course #{course_id} What it means
SisCourseImporter#add_course raises ImportError when the status value does not match /\A(active|deleted|completed|unpublished|published)/i. SIS course rows must declare a recognized workflow state; any other string is rejected before touching the database.
Solutions
- Map the source system's status to one of active|deleted|completed|unpublished|published in the CSV export
- Strip leading whitespace from the status column (the regex anchors at \A, so " active" fails)
- Convert boolean/numeric exports (1/0, true/false) explicitly to "active"/"deleted"
- Add a schema check on the courses.csv before import that validates status against the allowed enum
Example fix
// before
status = row["state"] # "inactive"
// after
status = {"inactive"=>"deleted", "open"=>"active", "closed"=>"completed"}.fetch(row["state"], row["state"]).to_s.strip Defensive patterns
Strategy: validation
Validate before calling
VALID_COURSE_STATUSES = %w[active deleted completed unpublished published]
status = row['status'].to_s.strip
raise ArgumentError, "bad status #{status}" unless VALID_COURSE_STATUSES.any? { |s| status.downcase.start_with?(s) } Type guard
def valid_course_status?(s) = %w[active deleted completed unpublished published].any? { |v| s.to_s.strip.downcase.start_with?(v) } Try / catch
begin
importer.add_course(...)
rescue SisImport::ImportError => e
raise unless e.message.start_with?('Improper status')
log.error("unmapped course status in feed: #{e.message}")
end Prevention
- Maintain an explicit status mapping table between your SIS and Canvas enums
- Strip leading/trailing whitespace on enum columns before import
- Never emit booleans or numeric codes for status columns
- Unit-test the export against the Canvas status regex
When it happens
Trigger: add_course called with status set to anything outside {active, deleted, completed, unpublished, published} (case-insensitive, prefix match): e.g. "deleted", "Active ×3 typo” like "actve", "enable", "pending", numeric 1/0 passed as "1", or a nil status.
Common situations: Data teams mapping their SIS statuses to Canvas incorrectly (e.g. "inactive", "archived", "closed"); exports emitting booleans or numeric codes; locale-mutated column values; extra whitespace at the START (leading space breaks the \A anchor even though trailing text is allowed).
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- Invalid course_format "#
- Invalid grade_passback_setting "#
- Improper grade_publishing_status "#
- Improper status "# " for an enrollment
- Improper status "# " for a # user
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/37468757ac8d7a5c.
Report an issue: GitHub.
Appendix: source
Thrown at lib/sis/course_importer.rb:69
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
course_enrollment_term_id_stuck = course.stuck_sis_fields.include?(:enrollment_term_id)
if !course_enrollment_term_id_stuck && term_id
term = @root_account.enrollment_terms.active.find_by(sis_source_id: term_id)View on GitHub (pinned to 1c9f0bb801)