instructure/canvas-lms · error · ImportError
Improper status for user #
Error message
Improper status for user #{user.user_id} What it means
add_user only accepts statuses beginning with active, suspended, or deleted (case-insensitive). Any other string — typos, alternate vocabularies like 'inactive' or 'enabled' — is rejected with this ImportError, since Canvas SIS imports have a fixed status vocabulary.
Solutions
- Map source statuses to the exact Canvas vocabulary: active, suspended, deleted
- Strip/normalize the status string (downcase, trim) before building the Models::User
- Treat unrecognized statuses as 'active' only after confirming that matches your policy
- Log and skip rows with unmappable statuses
Example fix
// before
user = Sis::Models::User.new(user_id: id, login_id: login, status: hr_status) # e.g. 'inactive'
// after
mapped = { 'active' => 'active', 'enabled' => 'active', 'inactive' => 'suspended', 'terminated' => 'deleted' }.fetch(hr_status.downcase.strip, nil)
raise ArgumentError, "unmapped status #{hr_status}" unless mapped
user = Sis::Models::User.new(user_id: id, login_id: login, status: mapped) Defensive patterns
Strategy: validation
Validate before calling
unless user.status.match?(/\A(active|suspended|deleted)/i) user.status = STATUS_MAP.fetch(user.status.to_s.downcase.strip, 'active') end
Type guard
VALID_STATUSES = %w[active suspended deleted].freeze
def valid_status?(u)
u.is_a?(Sis::Models::User) && VALID_STATUSES.any? { |s| u.status.to_s.downcase.start_with?(s) }
end Try / catch
begin importer.add_user(user) rescue SisImports::ImportError => e unmappable << user.user_id end
Prevention
- Normalize and map external status vocabularies to active/suspended/deleted at ETL time
- Add an allowlist assertion before building Models::User
- Test with the full set of source-system status values
When it happens
Trigger: add_user with user.status like 'inactive', 'enabled', 'archived', or a localized/translated status word that does not match /\A(active|suspended|deleted)/i.
Common situations: Mapping another system's status vocabulary (e.g. HR 'inactive') directly into the SIS import; whitespace or translation issues in exports; someone setting 'deleted!' or 'deactivated' expecting it to work.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- Invalid type '# ' for change_sis_id
- No status 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 #…
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/691324d6a2c09d14.
Report an issue: GitHub.
Appendix: source
Thrown at lib/sis/user_importer.rb:78
@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?
user_row.full_name
elsif user_row.first_name.present? || user_row.last_name.present?View on GitHub (pinned to 1c9f0bb801)