{"record":{"id":"d02902bcfe65feb8","repo":"instructure/canvas-lms","slug":"a-student-referenced-a-non-existent-user-student-id","errorCode":null,"errorMessage":"A student referenced a non-existent user #{student_id}","messagePattern":"A student referenced a non-existent user #(.+?)","errorType":"validation","errorClass":"ImportError","httpStatus":null,"severity":"error","filePath":"lib/sis/user_observer_importer.rb","lineNumber":67,"sourceCode":"        @logger = logger\n        @success_count = 0\n\n        @users_to_update_account_associations = Set.new\n        @user_observers_to_update_sis_batch_ids = []\n      end\n\n      def process_user_observer(observer_id, student_id, status)\n        raise ImportError, \"No observer_id given for a user observer\" if observer_id.blank?\n        raise ImportError, \"No user_id given for a user observer\" if student_id.blank?\n        raise ImportError, \"Can't observe yourself user #{student_id}\" if student_id == observer_id\n        raise ImportError, \"Improper status \\\"#{status}\\\" for a user_observer\" unless /\\A(active|deleted)\\z/i.match?(status)\n        return if @batch.skip_deletes? && status =~ /deleted/i\n\n        o_pseudo = @root_account.pseudonyms.active.find_by(sis_user_id: observer_id)\n        raise ImportError, \"An observer referenced a non-existent user #{observer_id}\" unless o_pseudo\n\n        s_pseudo = @root_account.pseudonyms.active.find_by(sis_user_id: student_id)\n        raise ImportError, \"A student referenced a non-existent user #{student_id}\" unless s_pseudo\n\n        observer = o_pseudo.user\n        student = s_pseudo.user\n        raise ImportError, \"Can't observe yourself user #{student_id}\" if observer == student\n\n        add_remove_observer(observer, student, observer_id, student_id, status)\n      end\n\n      def add_remove_observer(observer, student, observer_id, student_id, status)\n        case status.downcase\n        when \"active\"\n          check_observer_notification_settings(observer)\n          user_observer = UserObservationLink.create_or_restore(observer:, student:, root_account: @root_account)\n        when \"deleted\"\n          user_observer = observer.as_observer_observation_links.for_root_accounts(@root_account).find_by(user_id: student)\n          if user_observer\n            user_observer.destroy\n          else","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/sis/user_observer_importer.rb#L49-L85","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Verify the student exists and is active: Account#pseudonyms.active.find_by(sis_user_id: student_id); fix or remove the row if not.","Ensure the students users.csv (or users import) is processed in the same batch BEFORE the observers file, or in an earlier batch.","Correct the student_id spelling / confirm it matches the sis_user_id on the student's pseudonym, not their login or canvas ID.","Re-activate the student's pseudonym if it was suspended/deleted, then re-run the SIS import."],"exampleFix":"# before (observers.csv references unknown student)\nobserver_id,student_id,status\nuser:obs1,user:studp99,active\n\n# after (student must exist first in users.csv with matching sis_user_id)\nusers.csv:  user_id,login_id,status\n            studp99,studp99,active\nobservers.csv: user:obs1,user:studp99,active","handlingStrategy":"validation","validationCode":"# before calling the SIS import, verify the student exists\nstudent_id = 'studp99'\nunless root_account.pseudonyms.active.exists?(sis_user_id: student_id)\n  raise \"Skipping observer row: student #{student_id} not found/active\"\nend","typeGuard":null,"tryCatchPattern":"begin\n  importer.process_user_observer(obs_id, stud_id, status)\nrescue SIS::ImportError => e\n  Rails.logger.warn(\"observer row skipped: #{e.message}\")\n  # mark row as failed and continue the batch\nend","preventionTips":["Import users.csv before user_observers.csv in every batch","Validate observer/student SIS IDs against pseudonyms before generating import files","Keep sis_user_ids stable; never rename them between exports","Deactivate observer rows when the student is deleted"],"tags":["sis","import","record-not-found","ruby"],"backgroundTag":"record-not-found","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}