instructure/canvas-lms · error
course hasn't been converted
Error message
course hasn't been converted
What it means
SelectiveContentFormatter#provides_content_list formats the migration content inventory. If no migration object is present, the migration is a course copy (content comes from the course itself), or there is no overview attachment, and none of the earlier branches apply, the formatter has no source to enumerate and raises this RuntimeError instead of silently returning an empty list.
Solutions
- Check @migration.migration_type and ensure the import is not a course_copy_importer when expecting overview-based content
- Verify the migration's overview_attachment exists (migration.attachment / exported_attachment) before calling get_content_list; if missing, re-run the export
- If content should come from the course, use a course_copy_importer migration type or call get_content_from_course directly
- Guard the call: only invoke get_content_list when migration.overview_attachment.present?
Example fix
// before
formatter = SelectiveContentFormatter.new(@migration)
list = formatter.get_content_list('assignments')
// after
if @migration.migration_type == 'course_copy_importer' || @migration.overview_attachment
list = SelectiveContentFormatter.new(@migration).get_content_list('assignments')
else
raise 'no overview attachment available for selective content'
end Defensive patterns
Strategy: validation
Validate before calling
raise 'no content source' unless migration&.migration_type == 'course_copy_importer' || migration&.overview_attachment
Type guard
def content_list_available?(migration) = !migration.nil? && (migration.migration_type == 'course_copy_importer' || migration.overview_attachment.present?)
Try / catch
begin list = formatter.get_content_list(type) rescue RuntimeError => e raise unless e.message == "course hasn't been converted" list = [] end
Prevention
- Confirm migration type and overview attachment before building the formatter
- Re-run exports that failed to produce an overview attachment
- Prefer course_copy_importer path when content lives in the course
When it happens
Trigger: Calling get_content_list on a formatter built with a @migration that is nil, has migration_type 'course_copy_importer' (handled by the first branch), and lacks an @migration.overview_attachment, so the elsif fails and the else raises. Concretely: a non-course-copy migration (e.g. a zip or QTI import) whose overview attachment was never generated or was deleted.
Common situations: Developers customizing selective content UIs hit this when testing migrations whose course export overview file wasn't attached (failed/interupted export), or when the formatter is constructed without a migration object in specs/scripts.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- No migration file found
- Assignment has self assessments or due date has passed
- Can't update submission scores unless it's completed
- cannot merge a test student
- Cannot move # # because it has # _memberships.
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/58d9647bf9602fe4.
Report an issue: GitHub.
Appendix: source
Thrown at lib/canvas/migration/helpers/selective_content_formatter.rb:61
def initialize(migration = nil, base_url = nil, global_identifiers:)
@migration = migration
@base_url = base_url
@global_identifiers = global_identifiers
end
def valid_type?(type = nil)
type.nil? || SELECTIVE_CONTENT_TYPES.any? { |t| t[0] == type } || type.start_with?("submodules_") || type.start_with?("learning_outcome_groups_")
end
def get_content_list(type = nil, source = nil)
raise "unsupported migration type" unless valid_type?(type)
if !@migration || @migration.migration_type == "course_copy_importer"
get_content_from_course(type, source)
elsif @migration.overview_attachment
get_content_from_overview(type)
else
raise "course hasn't been converted"
end
end
private
def property_prefix
@migration ? "copy" : "select"
end
# pulls the available items from the overview attachment on the content migration
def get_content_from_overview(type = nil)
course_data = Rails.cache.fetch(["migration_selective_cache", @migration.shard, @migration].cache_key, expires_in: 5.minutes) do
att = @migration.overview_attachment.open
data = JSON.parse(att.read)
data = separate_announcements(data)
data["attachments"] ||= data["file_map"]&.values
data["quizzes"] ||= data["assessments"]
data["context_modules"] ||= data["modules"]View on GitHub (pinned to 1c9f0bb801)