instructure/canvas-lms · error · ImportError
Cannot move # # because it has # _memberships.
Error message
Cannot move #{type_display_name(item_type)} #{item_id} because it has #{item_type}_memberships. What it means
Raised by can_change_context? when an existing group or differentiation tag with memberships would be moved to a different context in a disallowed way: any move of a populated differentiation tag, and any populated-group move involving a Course context (source or destination). This protects membership integrity during SIS imports.
Solutions
- Remove or reassign the item's memberships before moving it.
- Create a new group in the target context instead of moving the populated one.
- For groups, allow only non-Course context changes (account reorganization).
- Exclude the offending rows from the batch.
Example fix
# before: moving populated group to another course group.context = new_course group.save # after group.group_memberships.destroy_all group.context = new_course group.save
Defensive patterns
Strategy: try-catch
Validate before calling
if item.group_memberships.exists? && (new_context.is_a?(Course) || item.context.is_a?(Course) || item_type == 'differentiation_tag') && (new_context.id != item.context_id || new_context.class.base_class.name != item.context_type) raise ArgumentError, 'cannot move item with memberships' end
Try / catch
begin
importer.add_group(gid, cat_id, acct_id, course_id, name, status)
rescue ImportError => e
if e.message.start_with?('Cannot move')
logger.warn("Skipped move of populated item #{gid}")
else
raise
end
end Prevention
- Avoid reassigning context for populated groups/tags in re-imports
- Create new items in target contexts instead of moving populated ones
- Diff incoming CSV context columns against current item context
- Clear memberships deliberately before any move
When it happens
Trigger: add_group/add_tag updating an existing item (matched by sis id) whose group_memberships exist, where new_context differs from item.context and (either context is a Course, or item_type == 'differentiation_tag').
Common situations: Re-importing a groups.csv that reassigns a populated study group to another course; reorganizing account hierarchy with member-bearing groups; moving tags between courses mid-term.
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
- Cannot restore course #
- A course did not pass validation
- A # user did not pass validation (user: # , # : # , error…
- A (templated) course did not pass validation
- A # did not pass validation (# : # , error: # )
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/5b4659e33c2bf12e.
Report an issue: GitHub.
Appendix: source
Thrown at lib/sis/group_importer.rb:147
msg += item.errors.full_messages.join(",") + ")"
raise ImportError, msg
end
end
def can_change_context?(item, new_context, item_type, item_id)
if item_type == "differentiation_tag"
raise ImportError, "Differentiation Tags are not enabled for Account #{context.account.id}." unless new_context.account.allow_assign_to_differentiation_tags?
end
return true unless item&.group_memberships&.exists?
same_context = new_context.id == item.context_id &&
new_context.class.base_class.name == item.context_type
# For groups, we only block moves between courses, but allow other context changes
if !same_context &&
((item.context.is_a?(Course) || new_context.is_a?(Course)) || item_type == "differentiation_tag")
raise ImportError, "Cannot move #{type_display_name(item_type)} #{item_id} because it has #{item_type}_memberships."
end
true
end
public
def add_group(group_id, group_category_id, account_id, course_id, name, status)
return if invalid_import_item?(group_id, name, status, "group")
if course_id && account_id
raise ImportError, "Only one context is allowed and both course_id and account_id where provided for group #{group_id}."
end
context = find_context(account_id, :account_id, "group", group_id) if account_id
context ||= find_context(course_id, :course_id, "group", group_id) if course_id
group_category = nilView on GitHub (pinned to 1c9f0bb801)