instructure/canvas-lms · error · ImportError

No login_id given for user #

Error message

No login_id given for user #{user.user_id}

What it means

add_user requires a non-blank login_id for every user being imported; the login is the credential users sign in with and is key to matching existing logins. After user_id passes, a blank login_id raises ImportError naming the offending user_id so the row can be located in the source file.

Solutions

  1. Populate login_id (usually the institutional email) on every user record before add_user
  2. Pre-filter rows with blank login_id and report them as source-data errors
  3. Check that your CSV/XML parser maps the login/email column to login_id (not a differently named field)
  4. Use add_user_to_login or a login-only path only when a login genuinely exists

Example fix

// before
importer.add_user(Sis::Models::User.new(user_id: row['user_id'], status: row['status']))
// after
importer.add_user(Sis::Models::User.new(user_id: row['user_id'], login_id: row['login_id'] || row['email'], status: row['status']))
Defensive patterns

Strategy: validation

Validate before calling

next unless user.login_id.present?
importer.add_user(user)

Type guard

def has_login?(u)
  u.is_a?(Sis::Models::User) && u.login_id.present?
end

Try / catch

begin
  importer.add_user(user)
rescue SisImports::ImportError => e
  errors << { user_id: user.user_id, reason: e.message }
end

Prevention

When it happens

Trigger: add_user(user) where user.login_id is nil/empty — typically a CSV row missing the login_id (email) column, or a Models::User constructed without one; also hit when login_only: true imports only carry an existing_* id but still must supply a login.

Common situations: SIS exports where the email/login column is blank for some users; scripts that populate only user_id and status; column-rename in the source system after an upgrade.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at lib/sis/user_importer.rb:76

        @root_account = root_account
        @logger = logger
        @batched_users = []
        @messages = messages
        @success_count = 0

        @roll_back_data = []
        @users_to_set_sis_batch_ids = []
        @pseudos_to_set_sis_batch_ids = []
        @users_to_add_account_associations = []
        @users_to_update_account_associations = []
        @users_to_sync = Set.new
        @authentication_providers = {}
      end

      # Pass a single instance of SIS::Models::User
      def add_user(user, login_only: false)
        raise ImportError, "No user_id given for a user" if user.user_id.blank?
        raise ImportError, "No login_id given for user #{user.user_id}" if user.login_id.blank?
        raise ImportError, "No status given for user #{user.user_id}" if user.status.blank?
        raise ImportError, "Improper status for user #{user.user_id}" unless user.status.match?(/\A(active|suspended|deleted)/i)
        return if @batch.skip_deletes? && user.status.match?(/deleted/i)

        if login_only && user.existing_user_id.blank? && user.existing_integration_id.blank? && user.existing_canvas_user_id.blank?
          raise ImportError, I18n.t("No existing user provided for login with SIS ID %{user_id}", user_id: user.user_id)
        end

        @batched_users << user
        process_batch(login_only:) if @batched_users.size >= BATCH_SIZE
      end

      def any_left_to_process?
        !@batched_users.empty?
      end

      def infer_user_name(user_row, prior_name = nil)
        if user_row.full_name.present?

View on GitHub (pinned to 1c9f0bb801)