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
- Set existing_user_id (SIS id of the existing user) or existing_canvas_user_id before calling with login_only: true
- Resolve the matching user first (e.g. via Pseudonym/User lookup) and populate the existing_* field
- Drop login_only: true and do a full user import if the row may create a new user
- 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
- Resolve existing users before login-only imports and populate existing_* fields
- Only use login_only for known-existing users, not new rows
- Log which match key (SIS vs canvas id) was used for traceability
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
- No login_id given for user #
- A cross-listing referenced a non-existent section #
- A deleted cross-listing failed: #
- A new_id, '# ', referenced an existing # and the # with #…
- A new_integration_id, '#
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)