instructure/canvas-lms · error · ImportError
# (user # , login # )
Error message
#{pseudo.errors.first.full_message} (user #{user_row.user_id}, login #{user_row.login_id}) What it means
SisImports::ImportError raised in SIS::UserImporter#process_batch when pseudo.save_without_broadcasting fails validation on the user's Pseudonym (login record). The first error's full_message is wrapped with the sis user_id and login_id via generate_user_warning and raised; the surrounding rescue logs it into the batch messages for that row.
Solutions
- Check the embedded error message for which pseudonym attribute failed (typically 'login')
- Ensure login_id is present and unique per root account in users.csv; deduplicate rows
- Search existing users for the conflicting login and resolve the collision (rename or delete the stale record)
- Verify the login matches any account-level login format requirements
Example fix
// before: two rows share a login user_id,login_id 1001,jdoe 1002,jdoe // after user_id,login_id 1001,jdoe 1002,jdoe2
Defensive patterns
Strategy: validation
Validate before calling
logins = rows.map { |r| r[:login_id].to_s.strip.downcase }
if logins.uniq.size != logins.size
raise 'duplicate login_id values in users.csv'
end
raise 'blank login_id' if logins.include?('') Try / catch
begin
importer.add_user(row)
rescue SisImports::ImportError => e
raise unless e.message.include?('has already been taken')
# route duplicate-login rows to a reconciliation step
end Prevention
- Deduplicate login_id values across the CSV and against the account before import
- Normalize login casing/whitespace at CSV generation time
- Check account login format policies before choosing login schemes
When it happens
Trigger: During process_batch (called from add_user), the Pseudonym for the row fails validation on save — most commonly a blank login_id, a login_id already taken by another pseudonym in the account (unique-scope violation), or an invalid sis_user_id. pseudo.errors.first.full_message is then raised as ImportError.
Common situations: Duplicate login_id across two CSV rows or colliding with an existing non-SIS user; empty login column for a new user; case/whitespace variants of an existing login; login format rules (e.g. must be email-like) enforced by account settings.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- A user did not pass validation
- Not Found
- # (user # , login # )
- An email did not pass validation
- An institutional tag category did not pass validation
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/4379fc0893f65f19.
Report an issue: GitHub.
Appendix: source
Thrown at lib/sis/user_importer.rb:359
# need to call save first so we can get the user id
record_sync_for(user) if user_saved && user_changed
if !user_saved && !user.errors.empty?
message = generate_user_warning(user.errors.first.join(" "), user_row.user_id, user_row.login_id)
raise ImportError, message
end
elsif @batch
@users_to_set_sis_batch_ids << user.id
end
pseudo.user_id = user.id
if pseudo.changed?
pseudo.sis_batch_id = @batch.id if @batch
if pseudo.save_without_broadcasting
record_sync_for(user)
p_data = SisBatchRollBackData.build_data(sis_batch: @batch, context: pseudo)
@roll_back_data << p_data if p_data
elsif !pseudo.errors.empty?
message = generate_user_warning(pseudo.errors.first.full_message, user_row.user_id, user_row.login_id)
raise ImportError, message
end
end
end
rescue ImportError
@messages << SisBatch.build_error(user_row.csv, message, sis_batch: @batch, row: user_row.lineno, row_info: user_row.row)
next
rescue => e
# something broke
error = Canvas::Errors.capture_exception(:sis_import, e)
er = error[:error_report]
message = generate_user_warning("Something broke with this user. Contact Support with ErrorReport id: #{er}", user_row.user_id, user_row.login_id)
@messages << SisBatch.build_error(user_row.csv, message, sis_batch: @batch, row: user_row.lineno, backtrace: e.backtrace, row_info: user_row.row)
next
end
@users_to_add_account_associations << user.id if should_add_account_associations
@users_to_update_account_associations << user.id if should_update_account_associations
View on GitHub (pinned to 1c9f0bb801)