instructure/canvas-lms · error · ImportError
Can't delete a non-existent observer for observer: #
Error message
Can't delete a non-existent observer for observer: #{observer_id}, student: #{student_id} What it means
In add_remove_observer, a status of 'deleted' looks up the observer's existing UserObservationLink scoped to the root account for the given student. If no such link exists there is nothing to delete, so the importer raises ImportError. Only 'active' (create) and 'deleted' (destroy) statuses are supported, and deletes require an existing link.
Solutions
- Verify the UserObservationLink exists before importing the delete row (observer.as_observer_observation_links.for_root_accounts(root_account).find_by(user_id: student)).
- Remove the delete row if the link was already deleted in a prior batch — the delete is idempotent-unfriendly by design.
- Ensure the observer_id/student_id in the delete row exactly match those used in the original 'active' row.
- Check the link's root_account/shard; re-import against the account where the link actually lives.
Example fix
// before (delete for a link that never existed) user_id,associated_user_id,status user:obs1,user:studp99,deleted // after (create first, then delete) user:obs1,user:studp99,active # import 1 user:obs1,user:studp99,deleted # import 2 (only if still linked)
Defensive patterns
Strategy: validation
Validate before calling
# only emit delete rows for links that currently exist
link = observer.as_observer_observation_links
.for_root_accounts(root_account).find_by(user_id: student)
next unless link # skip delete rows with nothing to delete Try / catch
begin
importer.process_user_observer(obs_id, stud_id, 'deleted')
rescue SIS::ImportError => e
Rails.logger.info("already unlinked, ignoring: #{e.message}")
end Prevention
- Track which links were already deleted to avoid duplicate delete rows
- Use identical observer_id/student_id values as the original create row
- Verify link's root_account matches the import's root account
- Diff source-system state against Canvas before generating delete files
When it happens
Trigger: A user_observers.csv row with status=deleted where the observer was never linked to that student via this root account: the link was already removed by a previous import, the link was created under a different root account/shard, the observer_id or student_id in the delete row differs from the one used when creating the link, or duplicate delete rows in one batch.
Common situations: Re-processing an old SIS batch after the link was already deleted, exporting 'deleted' rows from a source system without checking current state, ID normalization differences between the create and delete files, multi-shard setups where find_by(user_id: student) runs on the wrong root account.
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
- A student referenced a non-existent user #
- A cross-listing referenced a non-existent section #
- A new_id, '# ', referenced an existing # and the # with #…
- A new_integration_id, '#
- An old_id, '# ', referenced a different # than the…
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/44461eb526eac5db.
Report an issue: GitHub.
Appendix: source
Thrown at lib/sis/user_observer_importer.rb:86
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
raise ImportError, "Can't delete a non-existent observer for observer: #{observer_id}, student: #{student_id}"
end
end
raise ImportError, "Failed to return user observer for observer: #{observer_id}, student: #{student_id}" unless user_observer
@users_to_update_account_associations.add observer.id
@user_observers_to_update_sis_batch_ids << user_observer.id
@success_count += 1
end
def check_observer_notification_settings(observer)
if @root_account.settings[:default_notifications_disabled_for_observers]
observer.default_notifications_disabled = true
observer.save if observer.changed?
end
end
end
end
endView on GitHub (pinned to 1c9f0bb801)