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
- Exclude fake students from the merge candidate list before calling into()
- Check from_user.preferences[:fake_student] || target_user.preferences[:fake_student] and abort gracefully with a message instead of raising
- Remove/recreate the test student data separately; test students can be deleted from a course instead of merged
- 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
- Exclude fake students from merge UI and admin tooling
- Filter preferences[:fake_student] in bulk user-consolidation scripts
- Delete test students from courses rather than merging them
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
- Assignment has self assessments or due date has passed
- Can't update submission scores unless it's completed
- Cannot move # # because it has # _memberships.
- Cannot restore course #
- course hasn't been converted
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)