instructure/canvas-lms · error · ImportError
Cannot move # # because it has # in it.
Error message
Cannot move #{type_display} #{sis_id} because it has #{item_type} in it. What it means
check_context_movement guards against moving a group category / differentiation tag set to a different context while it still contains active groups (or tags). Such a move would orphan or mis-scope the child groups, so the importer raises ImportError instead of silently relocating them.
Solutions
- Delete the active groups/tags in the category first (or import their deletion), then move the category.
- Keep the category's context (account_id/course_id) unchanged in the CSV if it already exists with groups.
- Use a new sis_id for the category in the new context instead of moving the existing one.
- Manually move groups in the UI/API before changing the category's context in SIS.
Example fix
// before (category gc-1 has groups, moving from COURSE-42 to COURSE-7) gc-1,Math Groups,,COURSE-7,active // after (keep original context, or delete groups first) gc-1,Math Groups,,COURSE-42,active
Defensive patterns
Strategy: validation
Validate before calling
if category && category.groups.active.exists?
moving = category.context_id != context.id ||
category.context_type != context.class.base_class.name
raise ArgumentError, "cannot move #{category.sis_source_id}: it still has active groups" if moving
end Try / catch
begin importer.add_group_category(sis_id, acct_id, course_id, name, status) rescue SIS::GroupCategoryImporter::ImportError => e if e.message =~ /Cannot move/ then flag_for_manual_review(sis_id) else raise end end
Prevention
- Treat a category's context as immutable once groups exist under it
- Delete or move child groups before changing the category's account/course
- Diff each import against prior state to spot context changes for existing sis_ids
When it happens
Trigger: add_group_category / add_differentiation_tag_set imports a sis_id that already exists (category.groups.active.exists?) but whose context_id/context_type differs from the resolved context of the current row — e.g., the row now names a different course or account.
Common situations: Changing a category's course_id or account_id in the CSV after groups were created inside it; re-parenting a tag set to another course while it still has tags; merged/re-imported CSVs where the same sis_id maps to two different courses.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- A # did not pass validation (# : # , error: # )
- Account with id "# " didn't exist for # #
- Cannot restore sub_account with ID: #
- Course with id "# " didn't exist for # #
- Differentiation Tags are not enabled for Account #
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/070993dca2f5c9a5.
Report an issue: GitHub.
Appendix: source
Thrown at lib/sis/group_category_importer.rb:87
context = @courses_cache[course_id]
context ||= @root_account.all_courses.active.find_by(sis_source_id: course_id)
raise ImportError, "Course with id \"#{course_id}\" didn't exist for #{type_display} #{sis_id}" unless context
@courses_cache[context.sis_source_id] = context
elsif course_id.nil? && type_display == "differentiation tag set"
raise ImportError, "No course_id given for #{type_display} #{sis_id}"
end
context || @root_account
end
def check_context_movement(category, context, sis_id, type_display)
if category && category.groups.active.exists? &&
!(context.id == category.context_id && context.class.base_class.name == category.context_type)
item_type = type_display.include?("tag") ? "tags" : "groups"
raise ImportError, "Cannot move #{type_display} #{sis_id} because it has #{item_type} in it."
end
end
def apply_status(category, status)
case status
when /active/i
category.deleted_at = nil
when /deleted/i
category.deleted_at = Time.zone.now
end
end
def save_category(category, sis_id, type_display)
if category.save
data = build_data(category)
@roll_back_data << data if data
@success_count += 1
trueView on GitHub (pinned to 1c9f0bb801)