instructure/canvas-lms · error · ActionController::RoutingError

Not Found

Error message

Not Found

What it means

After passing argument validation, process_user_observer looks up the observer's pseudonym via @root_account.pseudonyms.active.find_by(sis_user_id: observer_id). If no active pseudonym with that sis_user_id exists on the root account, this ImportError is raised: the observer_id does not reference an existing, active user in this account.

Solutions

  1. Import the observer user first (users.csv) so an active pseudonym with that sis_user_id exists on the root account.
  2. Verify the observer_id matches the pseudonym's sis_user_id exactly and is on the same root account.
  3. Check the pseudonym was not deleted (workflow_state inactive); re-activate or re-import the user.
  4. If the observer's user_id vs sis_user_id distinction is the issue, use the correct identifier.

Example fix

// before: observer referenced before being imported
# users.csv: (no OBS009 row)
# user_observers.csv: STU001,OBS009,active  -> ImportError

// after: import observer first
# users.csv: OBS009,parent@example.com,Parent,active
# user_observers.csv: STU001,OBS009,active
Defensive patterns

Strategy: validation

Validate before calling

exists = root_account.pseudonyms.active.exists?(sis_user_id: observer_id)
raise 'observer SIS ID not found on root account' unless exists

Try / catch

begin
  importer.process_user_observer(obs_id, stu_id, status)
rescue SIS::Import::ImportError => e
  logger.warn("observer #{obs_id} not found: #{e.message}")
end

Prevention

When it happens

Trigger: observer_id contains an SIS ID never imported on this root account; the observer's pseudonym was soft-deleted (inactive) or belongs to a different root account/subaccount; the ID differs by case or whitespace from the stored sis_user_id.

Common situations: Cross-account SIS imports where the observer was provisioned in another root account; observers deleted earlier in the same batch or previously via SIS; typos or stale IDs from a prior year's export; users whose integration_id was given instead of sis_user_id.

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

Appendix: source

Thrown at app/controllers/application_controller.rb:3312

    common_courses = courses.map do |course|
      course_json(course, current_user, session, ["html_url"], false).merge({
                                                                              roles: common_courses[course.id].map { |role| Enrollment.readable_type(role) }
                                                                            })
    end

    common_groups = groups.map do |group|
      group_json(group, current_user, session, include: ["html_url"]).merge({
                                                                              # in the future groups will have more roles and we'll need soemthing similar to
                                                                              # the roles.map above in courses
                                                                              roles: [t("#group.memeber", "Member")]
                                                                            })
    end

    common_courses + common_groups
  end

  def not_found
    raise ActionController::RoutingError, "Not Found"
  end

  def set_js_rights(objtypes = nil)
    objtypes ||= js_rights if respond_to?(:js_rights)
    if objtypes
      hash = {}
      objtypes.each do |instance_symbol|
        instance_name = instance_symbol.to_s
        obj = instance_variable_get(:"@#{instance_name}")
        policy = obj.check_policy(@current_user, session) unless obj.nil? || !obj.respond_to?(:check_policy)
        hash[:"#{instance_name.upcase}_RIGHTS"] = ActiveSupport::HashWithIndifferentAccess[policy.map { |right| [right, true] }] unless policy.nil?
      end

      js_env hash
    end
  end

  def set_js_wiki_data(opts = {})

View on GitHub (pinned to 1c9f0bb801)