{"record":{"id":"4ab34abe023528ba","repo":"instructure/canvas-lms","slug":"cyclic-reference-detected-when-importing-vendor-guid","errorCode":null,"errorMessage":"Cyclic reference detected when importing: %{vendor_guid}","messagePattern":"Cyclic reference detected when importing: %(.+?)","errorType":"exception","errorClass":"InvalidDataError","httpStatus":null,"severity":"error","filePath":"lib/outcomes/import.rb","lineNumber":298,"sourceCode":"\n    def create_rubric(ratings, mastery_points)\n      rubric = {}\n      rubric[:enable] = true\n      rubric[:mastery_points] = mastery_points\n      rubric[:ratings] = ratings.map.with_index { |v, i| [i, v] }.to_h\n      rubric\n    end\n\n    def root_parent(given_context)\n      @root_parents ||= {}\n      @root_parents[given_context] ||= LearningOutcomeGroup.find_or_create_root(given_context, true)\n    end\n\n    def find_parents(object, given_context, allow_indirect: false, model: nil)\n      if !model.nil? && !model.new_record? && object[:learning_outcome_group_id]\n        parent_group = LearningOutcomeGroup.find(object[:learning_outcome_group_id])\n        if parent_group.ancestor_ids.member?(model.id)\n          raise InvalidDataError, I18n.t(\n            \"Cyclic reference detected when importing: %{vendor_guid}\",\n            vendor_guid: object[:vendor_guid]\n          )\n        end\n      end\n      if object[:parent_guids].nil? || object[:parent_guids].blank?\n        group = [LearningOutcomeGroup.find(object[:learning_outcome_group_id])] if object[:learning_outcome_group_id]\n        group ||= [root_parent(given_context)]\n\n        return group\n      end\n\n      guids = object[:parent_guids].strip.split.uniq\n      possible_parents = LearningOutcomeGroup.where(outcome_import_id:, vendor_guid: guids)\n\n      # If allow_indirect is true, we could `filter{|g| child_context?(g.context) }`, but it is costly and\n      # redundant (since outcome_import_id matching is already enforced)\n      possible_parents = possible_parents.where(context: given_context) unless allow_indirect","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/outcomes/import.rb#L280-L316","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: group 'A' row declares parent = group 'B', but B is a child of A\n{\n  \"vendor_guid\": \"A\",\n  \"learning_outcome_group_id\": <B id>\n}\n// after: parent A at the top level\n{\n  \"vendor_guid\": \"A\"\n}","handlingStrategy":"validation","validationCode":"// Ruby: ensure no cycle before setting the parent\ndef safe_parent?(group, parent_group)\n  group.new_record? || !parent_group.ancestor_ids.member?(group.id)\nend","typeGuard":null,"tryCatchPattern":"begin\n  importer.import_object(...)\nrescue Outcomes::Import::InvalidDataError => e\n  raise unless e.message.include?('Cyclic reference')\n  # fix parent_guids / learning_outcome_group_id and retry\nend","preventionTips":["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."],"tags":["outcomes","import","cycle-detection","graph"],"backgroundTag":"cyclic-reference-detected","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}