instructure/canvas-lms · error · ImportError

A student referenced a non-existent user #

Error message

A student referenced a non-existent user #{student_id}

What it means

During SIS user-observer import, process_user_observer looks up the student by sis_user_id among the root account's active pseudonyms. If no active pseudonym matches the given student_id, the importer cannot resolve the student and raises ImportError. This guards against creating observation links to users that do not exist (or are inactive/deleted) in Canvas.

Solutions

  1. Verify the student exists and is active: Account#pseudonyms.active.find_by(sis_user_id: student_id); fix or remove the row if not.
  2. Ensure the students users.csv (or users import) is processed in the same batch BEFORE the observers file, or in an earlier batch.
  3. Correct the student_id spelling / confirm it matches the sis_user_id on the student's pseudonym, not their login or canvas ID.
  4. Re-activate the student's pseudonym if it was suspended/deleted, then re-run the SIS import.

Example fix

# before (observers.csv references unknown student)
observer_id,student_id,status
user:obs1,user:studp99,active

# after (student must exist first in users.csv with matching sis_user_id)
users.csv:  user_id,login_id,status
            studp99,studp99,active
observers.csv: user:obs1,user:studp99,active
Defensive patterns

Strategy: validation

Validate before calling

# before calling the SIS import, verify the student exists
student_id = 'studp99'
unless root_account.pseudonyms.active.exists?(sis_user_id: student_id)
  raise "Skipping observer row: student #{student_id} not found/active"
end

Try / catch

begin
  importer.process_user_observer(obs_id, stud_id, status)
rescue SIS::ImportError => e
  Rails.logger.warn("observer row skipped: #{e.message}")
  # mark row as failed and continue the batch
end

Prevention

When it happens

Trigger: A user_observers.csv row whose student_id column contains an sis_user_id that is not an active pseudonym on the root account: the student was never imported, was deleted, their pseudonym is not 'active' (suspended), the sis_user_id was renamed, or the observer row was processed before the student row in the same SIS batch.

Common situations: Out-of-order/missing rows in an SIS zip (observer referenced before student import), typos in sis_user_id, students deleted or deactivated between exports, importing observers into a sub-account/root account where the student's pseudonym lives on a different shard or account, re-using IDs across environments (test vs prod).

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/d02902bcfe65feb8. Report an issue: GitHub.

Appendix: source

Thrown at lib/sis/user_observer_importer.rb:67

        @logger = logger
        @success_count = 0

        @users_to_update_account_associations = Set.new
        @user_observers_to_update_sis_batch_ids = []
      end

      def process_user_observer(observer_id, student_id, status)
        raise ImportError, "No observer_id given for a user observer" if observer_id.blank?
        raise ImportError, "No user_id given for a user observer" if student_id.blank?
        raise ImportError, "Can't observe yourself user #{student_id}" if student_id == observer_id
        raise ImportError, "Improper status \"#{status}\" for a user_observer" unless /\A(active|deleted)\z/i.match?(status)
        return if @batch.skip_deletes? && status =~ /deleted/i

        o_pseudo = @root_account.pseudonyms.active.find_by(sis_user_id: observer_id)
        raise ImportError, "An observer referenced a non-existent user #{observer_id}" unless o_pseudo

        s_pseudo = @root_account.pseudonyms.active.find_by(sis_user_id: student_id)
        raise ImportError, "A student referenced a non-existent user #{student_id}" unless s_pseudo

        observer = o_pseudo.user
        student = s_pseudo.user
        raise ImportError, "Can't observe yourself user #{student_id}" if observer == student

        add_remove_observer(observer, student, observer_id, student_id, status)
      end

      def add_remove_observer(observer, student, observer_id, student_id, status)
        case status.downcase
        when "active"
          check_observer_notification_settings(observer)
          user_observer = UserObservationLink.create_or_restore(observer:, student:, root_account: @root_account)
        when "deleted"
          user_observer = observer.as_observer_observation_links.for_root_accounts(@root_account).find_by(user_id: student)
          if user_observer
            user_observer.destroy
          else

View on GitHub (pinned to 1c9f0bb801)