{"record":{"id":"b43de9249e0aafdf","repo":"instructure/canvas-lms","slug":"failed-to-return-user-observer-for-observer-observer-id","errorCode":null,"errorMessage":"Failed to return user observer for observer: #{observer_id}, student: #{student_id}","messagePattern":"Failed to return user observer for observer: #(.+?), student: #(.+?)","errorType":"validation","errorClass":"ImportError","httpStatus":null,"severity":"error","filePath":"lib/sis/user_observer_importer.rb","lineNumber":89,"sourceCode":"        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\n            raise ImportError, \"Can't delete a non-existent observer for observer: #{observer_id}, student: #{student_id}\"\n          end\n        end\n        raise ImportError, \"Failed to return user observer for observer: #{observer_id}, student: #{student_id}\" unless user_observer\n\n        @users_to_update_account_associations.add observer.id\n        @user_observers_to_update_sis_batch_ids << user_observer.id\n        @success_count += 1\n      end\n\n      def check_observer_notification_settings(observer)\n        if @root_account.settings[:default_notifications_disabled_for_observers]\n          observer.default_notifications_disabled = true\n          observer.save if observer.changed?\n        end\n      end\n    end\n  end\nend\n","sourceCodeStart":71,"sourceCodeEnd":105,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/sis/user_observer_importer.rb#L71-L105","documentation":"After the status case block in add_remove_observer, the importer asserts that a user_observer record was produced — either created/restored for status 'active' or found-and-destroyed for 'deleted'. If the variable is nil/false the operation silently failed, so it raises ImportError. This is a post-condition check that the create_or_restore call (or delete path) actually yielded a usable link.","triggerScenarios":"UserObservationLink.create_or_restore returned nil for an 'active' row (e.g. creation failed validation, observer/student are in an invalid state, restore found nothing to restore), or a custom/patched status branch left user_observer unset (any status other than active/deleted that falls through the case without raising).","commonSituations":"Status values with unexpected casing or whitespace slipping past earlier regex checks, database constraint or validation failures swallowed inside create_or_restore, concurrent SIS batches racing on the same observer/student pair, locally patched importer code adding a status branch that forgets to assign user_observer.","solutions":["Inspect why UserObservationLink.create_or_restore returned nothing — check validation errors and uniqueness constraints on the link.","Confirm the status value in the CSV is exactly 'active' or 'deleted' (trimmed, correct case); unknown values fall through without setting user_observer.","Re-run the import after resolving conflicts from concurrent batches; avoid overlapping imports for the same user pair.","If locally customized, ensure every case branch assigns user_observer or raises its own clearer error."],"exampleFix":"# before (custom branch forgets to assign)\nwhen \"suspended\"\n  observer.as_observer_observation_links.for_root_accounts(@root_account).first&.suspend\n# after\ndef add_remove_observer(observer, student, observer_id, student_id, status)\n  case status.downcase\n  when \"suspended\"\n    user_observer = observer.as_observer_observation_links\n                         .for_root_accounts(@root_account).first&.tap(&:suspend)\n    raise ImportError, \"No link to suspend\" unless user_observer","handlingStrategy":"try-catch","validationCode":"# pre-check the link can be created\nnext unless observer && student && observer != student\nnext unless observer.account == student.account # same root account","typeGuard":null,"tryCatchPattern":"begin\n  importer.process_user_observer(obs_id, stud_id, status)\nrescue SIS::ImportError => e\n  raise unless e.message.start_with?('Failed to return user observer')\n  Rails.logger.error(\"create_or_restore failed for #{obs_id}/#{stud_id}: #{e.message}\")\n  raise # this indicates a real failure, usually worth surfacing\nend","preventionTips":["Keep status values strictly 'active' or 'deleted' (lowercase, trimmed)","Avoid running overlapping SIS batches for the same user pair","Check UserObservationLink validations/constraints when failures cluster","Re-verify any locally patched importer branches assign user_observer"],"tags":["sis","import","post-condition","ruby"],"backgroundTag":"internal-invariant-violation","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"}