instructure/canvas-lms · error · ImportError

No existing user provided for login with SIS ID

Error message

No existing user provided for login with SIS ID %{user_id}

What it means

When add_user is called with login_only: true, the call is meant to attach/repair a login on an EXISTING Canvas user, so the record must identify that user via existing_user_id, existing_integration_id, or existing_canvas_user_id. If none is set, ImportError (I18n-translated) is raised because a login cannot be created for an unknown user.

Solutions

  1. Set existing_user_id (SIS id of the existing user) or existing_canvas_user_id before calling with login_only: true
  2. Resolve the matching user first (e.g. via Pseudonym/User lookup) and populate the existing_* field
  3. Drop login_only: true and do a full user import if the row may create a new user
  4. Use a non-login-only path for new users and reserve login_only for repairs

Example fix

// before
importer.add_user(user, login_only: true) # user.existing_user_id blank
// after
user.existing_user_id = user.user_id # or canvas user id in existing_canvas_user_id
importer.add_user(user, login_only: true)
Defensive patterns

Strategy: validation

Validate before calling

if login_only && user.existing_user_id.blank? && user.existing_integration_id.blank? && user.existing_canvas_user_id.blank?
  raise ArgumentError, 'login_only import requires an existing user reference'
end

Type guard

def resolvable_for_login_only?(u)
  u.existing_user_id.present? || u.existing_integration_id.present? || u.existing_canvas_user_id.present?
end

Try / catch

begin
  importer.add_user(user, login_only: true)
rescue SisImports::ImportError => e
  Rails.logger.warn("cannot attach login: #{e.message}")
end

Prevention

When it happens

Trigger: importer.add_user(user, login_only: true) where user has only user_id/login_id but existing_user_id, existing_integration_id, and existing_canvas_user_id are all blank.

Common situations: Re-running a user import in login-only mode against rows that were originally new-user rows; scripts that clear the existing_* match fields; a CSV that relies on SIS user_id matching but that path is only honored via existing_user_id in this mode.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — 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/f040182c3e948d1b. Report an issue: GitHub.

Appendix: source

Thrown at lib/sis/user_importer.rb:82

        @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?
          user_row.full_name
        elsif user_row.first_name.present? || user_row.last_name.present?
          [user_row.first_name, user_row.last_name].join(" ")
        elsif prior_name.present?
          prior_name
        elsif user_row.sortable_name.present?

View on GitHub (pinned to 1c9f0bb801)