{"record":{"id":"4ec3879f668865c4","repo":"instructure/canvas-lms","slug":"a-new-id-data-change-new-id-referenced-an-existing-type-and","errorCode":null,"errorMessage":"A new_id, '#{data_change.new_id}', referenced an existing #{type} and the #{type} with #{column} '#{data_change.old_id}' was not updated","messagePattern":"A new_id, '#(.+?)', referenced an existing #(.+?) and the #(.+?) with #(.+?) '#(.+?)' was not updated","errorType":"exception","errorClass":"ImportError","httpStatus":null,"severity":"error","filePath":"lib/sis/change_sis_id_importer.rb","lineNumber":128,"sourceCode":"        if data_change.new_id.present?\n          updates[column] = data_change.new_id\n        end\n        if data_change.new_integration_id.present?\n          updates[\"integration_id\"] = data_change.new_integration_id\n          if data_change.new_integration_id == \"<delete>\"\n            updates[\"integration_id\"] = nil\n          end\n        end\n        updates\n      end\n\n      def check_for_conflicting_ids(column, details, type, data_change)\n        if type == \"group_category\" && (data_change.old_integration_id || data_change.new_integration_id)\n          raise ImportError, \"Group categories should not have integration IDs.\"\n        end\n\n        check_new = details[:scope].where(column => data_change.new_id).exists? if data_change.new_id.present?\n        raise ImportError, \"A new_id, '#{data_change.new_id}', referenced an existing #{type} and the #{type} with #{column} '#{data_change.old_id}' was not updated\" if check_new\n\n        check_int = details[:scope].where(integration_id: data_change.new_integration_id).exists? if data_change.new_integration_id.present?\n        raise ImportError, \"A new_integration_id, '#{data_change.new_integration_id}', referenced an existing #{type} and the #{type} with integration_id '#{data_change.old_integration_id}' was not updated\" if check_int\n      end\n\n      def find_item_to_update(column, details, type, data_change)\n        if data_change.old_id.present?\n          old_item = details[:scope].find_by(column => data_change.old_id)\n        end\n        if data_change.old_integration_id.present?\n          old_int_item = details[:scope].find_by(integration_id: data_change.old_integration_id)\n        end\n        if data_change.old_id.present? && data_change.old_integration_id.present?\n          raise ImportError, \"An old_id, '#{data_change.old_id}', referenced a different #{type} than the old_integration_id, '#{data_change.old_integration_id}'\" unless old_item == old_int_item\n\n          return old_item\n        end\n        if data_change.old_id.present? && data_change.old_integration_id.blank?","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/sis/change_sis_id_importer.rb#L110-L146","documentation":"check_for_conflicting_ids raises this when a non-blank new_id already matches an existing record of the given type under the scope's SIS column (sis_source_id or sis_user_id for users). Re-keying an old record onto an identifier that is already taken would create a collision, so the update is refused and the old record left untouched.","triggerScenarios":"change_sis_id row where scope.where(column => new_id).exists? is true — e.g. swapping two records' SIS ids without first freeing one, or retrying a change that already succeeded.","commonSituations":"Renaming SIS ids in circles (A→B while B still holds B), duplicate rows in the same CSV, idempotent re-runs of an import that already applied the rename.","solutions":["Free the target identifier first (change the other record to a temporary id), then re-run this change.","Verify the new_id is not already in use: root_account.all_courses.where(sis_source_id: new_id).exists? etc.","Remove duplicate/replayed rows from the import batch."],"exampleFix":"// before: new_id already taken\nuser,U1,U-TAKEN\n// after: two-step swap via temp id\nuser,U1,TEMP-U\nuser,U-TAKEN,U1\nuser,TEMP-U,U-TAKEN","handlingStrategy":"validation","validationCode":"scope = case dc.type\n  when 'user' then root_account.pseudonyms\n  when 'course' then root_account.all_courses\n  # ... etc\nend\ncolumn = dc.type == 'user' ? :sis_user_id : :sis_source_id\nif dc.new_id.present? && scope.where(column => dc.new_id).exists?\n  raise ArgumentError, \"new_id already taken: #{dc.new_id}\"\nend\nprocess_change_sis_id(dc)","typeGuard":null,"tryCatchPattern":"begin\n  process_change_sis_id(dc)\nrescue SIS::ImportError => e\n  if e.message.include?('referenced an existing')\n    logger.warn(\"collision on new_id #{dc.new_id}; row skipped\")\n  end\nend","preventionTips":["Check uniqueness of new_id in the target scope before generating the row.","For id swaps, stage through a temporary unique id.","Deduplicate batch files to avoid replaying already-applied changes."],"tags":["sis","ruby","import","uniqueness","conflict"],"backgroundTag":"duplicate-identifier-conflict","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"}