instructure/canvas-lms · error · InvalidDataError
Cyclic reference detected when importing
Error message
Cyclic reference detected when importing: %{vendor_guid} What it means
find_parents guards against outcome group hierarchy cycles. When importing a row that specifies an explicit learning_outcome_group_id parent and the model already exists, if the proposed parent group has the model itself among its ancestors, making it the parent would create a cycle in the group tree, so the import raises this InvalidDataError.
Solutions
- Inspect the group tree and remove the row (or change learning_outcome_group_id) so no group becomes its own ancestor.
- Reorder the import so parents are imported before children and parent_guids reflect the desired final tree.
- First move the affected group out of the cycle in the UI/API, then re-run the import.
Example fix
// before: group 'A' row declares parent = group 'B', but B is a child of A
{
"vendor_guid": "A",
"learning_outcome_group_id": <B id>
}
// after: parent A at the top level
{
"vendor_guid": "A"
} Defensive patterns
Strategy: validation
Validate before calling
// Ruby: ensure no cycle before setting the parent def safe_parent?(group, parent_group) group.new_record? || !parent_group.ancestor_ids.member?(group.id) end
Try / catch
begin
importer.import_object(...)
rescue Outcomes::Import::InvalidDataError => e
raise unless e.message.include?('Cyclic reference')
# fix parent_guids / learning_outcome_group_id and retry
end Prevention
- Draw the intended group tree before re-parenting imports and verify it is acyclic.
- Order rows so parents always precede children.
- Never set a group's parent to one of its descendants.
When it happens
Trigger: Importing group A with learning_outcome_group_id pointing to a group B where B.ancestor_ids includes A's id (e.g. setting A's parent to its own descendant or itself); lib/outcomes/import.rb:298, called from import_group/import_outcome.
Common situations: Hand-edited import files rearranging group nesting; round-tripped exports where group order changed so a child is processed before its parent's parent; scripted re-parenting that forgot the tree is acyclic.
Related errors
- Cannot modify outcome from another context
- Database error ( )
- Error importing outcomes
- File has no data
- File has no outcomes data
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/4ab34abe023528ba.
Report an issue: GitHub.
Appendix: source
Thrown at lib/outcomes/import.rb:298
def create_rubric(ratings, mastery_points)
rubric = {}
rubric[:enable] = true
rubric[:mastery_points] = mastery_points
rubric[:ratings] = ratings.map.with_index { |v, i| [i, v] }.to_h
rubric
end
def root_parent(given_context)
@root_parents ||= {}
@root_parents[given_context] ||= LearningOutcomeGroup.find_or_create_root(given_context, true)
end
def find_parents(object, given_context, allow_indirect: false, model: nil)
if !model.nil? && !model.new_record? && object[:learning_outcome_group_id]
parent_group = LearningOutcomeGroup.find(object[:learning_outcome_group_id])
if parent_group.ancestor_ids.member?(model.id)
raise InvalidDataError, I18n.t(
"Cyclic reference detected when importing: %{vendor_guid}",
vendor_guid: object[:vendor_guid]
)
end
end
if object[:parent_guids].nil? || object[:parent_guids].blank?
group = [LearningOutcomeGroup.find(object[:learning_outcome_group_id])] if object[:learning_outcome_group_id]
group ||= [root_parent(given_context)]
return group
end
guids = object[:parent_guids].strip.split.uniq
possible_parents = LearningOutcomeGroup.where(outcome_import_id:, vendor_guid: guids)
# If allow_indirect is true, we could `filter{|g| child_context?(g.context) }`, but it is costly and
# redundant (since outcome_import_id matching is already enforced)
possible_parents = possible_parents.where(context: given_context) unless allow_indirectView on GitHub (pinned to 1c9f0bb801)