instructure/canvas-lms · error · ImportError

A new_id, '# ', referenced an existing # and the # with #…

Error message

A new_id, '#{data_change.new_id}', referenced an existing #{type} and the #{type} with #{column} '#{data_change.old_id}' was not updated

What it means

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.

Solutions

  1. Free the target identifier first (change the other record to a temporary id), then re-run this change.
  2. Verify the new_id is not already in use: root_account.all_courses.where(sis_source_id: new_id).exists? etc.
  3. Remove duplicate/replayed rows from the import batch.

Example fix

// before: new_id already taken
user,U1,U-TAKEN
// after: two-step swap via temp id
user,U1,TEMP-U
user,U-TAKEN,U1
user,TEMP-U,U-TAKEN
Defensive patterns

Strategy: validation

Validate before calling

scope = case dc.type
  when 'user' then root_account.pseudonyms
  when 'course' then root_account.all_courses
  # ... etc
end
column = dc.type == 'user' ? :sis_user_id : :sis_source_id
if dc.new_id.present? && scope.where(column => dc.new_id).exists?
  raise ArgumentError, "new_id already taken: #{dc.new_id}"
end
process_change_sis_id(dc)

Try / catch

begin
  process_change_sis_id(dc)
rescue SIS::ImportError => e
  if e.message.include?('referenced an existing')
    logger.warn("collision on new_id #{dc.new_id}; row skipped")
  end
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/4ec3879f668865c4. Report an issue: GitHub.

Appendix: source

Thrown at lib/sis/change_sis_id_importer.rb:128

        if data_change.new_id.present?
          updates[column] = data_change.new_id
        end
        if data_change.new_integration_id.present?
          updates["integration_id"] = data_change.new_integration_id
          if data_change.new_integration_id == "<delete>"
            updates["integration_id"] = nil
          end
        end
        updates
      end

      def check_for_conflicting_ids(column, details, type, data_change)
        if type == "group_category" && (data_change.old_integration_id || data_change.new_integration_id)
          raise ImportError, "Group categories should not have integration IDs."
        end

        check_new = details[:scope].where(column => data_change.new_id).exists? if data_change.new_id.present?
        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

        check_int = details[:scope].where(integration_id: data_change.new_integration_id).exists? if data_change.new_integration_id.present?
        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
      end

      def find_item_to_update(column, details, type, data_change)
        if data_change.old_id.present?
          old_item = details[:scope].find_by(column => data_change.old_id)
        end
        if data_change.old_integration_id.present?
          old_int_item = details[:scope].find_by(integration_id: data_change.old_integration_id)
        end
        if data_change.old_id.present? && data_change.old_integration_id.present?
          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

          return old_item
        end
        if data_change.old_id.present? && data_change.old_integration_id.blank?

View on GitHub (pinned to 1c9f0bb801)