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

  1. Check the embedded error message for which pseudonym attribute failed (typically 'login')
  2. Ensure login_id is present and unique per root account in users.csv; deduplicate rows
  3. Search existing users for the conflicting login and resolve the collision (rename or delete the stale record)
  4. 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

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


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)