instructure/canvas-lms · error

cannot merge a test student

Error message

cannot merge a test student

What it means

UserMerge#into blocks merging when either the source (from_user) or target_user is a test student (fake_student preference). Test students are per-course synthetic users, so merging them into or out of a real account would corrupt enrollment history, so the merge aborts with this error.

Solutions

  1. Exclude fake students from the merge candidate list before calling into()
  2. Check from_user.preferences[:fake_student] || target_user.preferences[:fake_student] and abort gracefully with a message instead of raising
  3. Remove/recreate the test student data separately; test students can be deleted from a course instead of merged
  4. If a test student legitimately represents a real person, have that person enroll as themselves and merge those accounts

Example fix

// before
UserMerge.from(source_user).into(target_user)
// after
unless source_user.preferences[:fake_student] || target_user.preferences[:fake_student]
  UserMerge.from(source_user).into(target_user)
else
  Rails.logger.warn('skipping merge: test student involved')
end
Defensive patterns

Strategy: validation

Validate before calling

raise 'test student involved' if source_user.preferences[:fake_student] || target_user.preferences[:fake_student]

Type guard

def mergeable?(a, b) = !a.preferences[:fake_student] && !b.preferences[:fake_student]

Try / catch

begin
  UserMerge.from(source).into(target)
rescue RuntimeError => e
  raise unless e.message == 'cannot merge a test student'
  flash_error('test students cannot be merged')
end

Prevention

When it happens

Trigger: Calling user.merge_into(target) or UserMerge.from(user).into(target) where either user.preferences[:fake_student] is true — i.e. one of the users is the course test student created via 'Test Student' in course settings.

Common situations: Admin console merge tool used on a test student; scripts consolidating users that sweep up fake students; account merges that don't filter test students beforehand.

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


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

Appendix: source

Thrown at lib/user_merge.rb:44

  attr_accessor :target_user, :data, :merge_data

  def initialize(from_user)
    @from_user = from_user
    @target_user = nil
    @merge_data = nil
    @data = []
  end

  def pseudonyms_to_move_in_this_shard
    Pseudonym.where(user_id: from_user)
  end

  def handle_instructure_identity(pseudonyms_to_move) end

  def into(target_user, merger: nil, source: nil)
    return unless target_user
    return if target_user == from_user
    raise "cannot merge a test student" if from_user.preferences[:fake_student] || target_user.preferences[:fake_student]

    @target_user = target_user
    @merger = merger
    target_user.associate_with_shard(from_user.shard, :shadow)
    # we also store records for the from_user on the target shard for a split
    from_user.associate_with_shard(target_user.shard, :shadow)
    target_user.shard.activate do
      @merge_data = UserMergeData.create!(user: target_user, from_user:, workflow_state: "merging")
      @merge_data.items.create!(user: target_user, item_type: "logs", item: { merger_id: merger.id, source: }.to_s) if merger || source

      items = []
      if target_user.avatar_state == :none && from_user.avatar_state != :none
        %i[avatar_image_source avatar_image_url avatar_image_updated_at avatar_state].each do |attr|
          items << merge_data.items.new(user: from_user, item_type: attr.to_s, item: from_user[attr]) if from_user[attr]
          target_user[attr] = from_user[attr]
        end
      end

View on GitHub (pinned to 1c9f0bb801)