instructure/canvas-lms · error · ActiveRecord::RecordNotFound

Context must be of type '#

Error message

Context must be of type '#{klass}'

What it means

process_user_observer raises this ImportError when student_id equals observer_id — a user cannot be their own observer. The check is a plain string equality on the two SIS IDs, done before any DB lookups, so even identical IDs that resolve to no user still raise this rather than a not-found error.

Solutions

  1. Correct the observer_id in the row to a different user's SIS ID.
  2. Filter self-pairings out in the pre-import validation step.
  3. If self-observation was intended as a no-op, omit the row entirely.

Example fix

// before (CSV)
user_id,observer_id
STU001,STU001

// after
user_id,observer_id
STU001,OBS009
Defensive patterns

Strategy: validation

Validate before calling

raise 'observer cannot observe themselves' if observer_id.to_s == student_id.to_s

Try / catch

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

Prevention

When it happens

Trigger: A users_observers CSV row where the observer_id and user_id columns contain the same SIS ID (copy-paste error); calling process_user_observer('X', 'X', 'active') programmatically.

Common situations: Spreadsheet fill-down copying the student's own ID into the observer column; bulk scripts that compute observer_id from the same variable as student_id; parents provisioned with a single SIS ID used for both roles.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/9b55df8eb9ac724d. Report an issue: GitHub.

Appendix: source

Thrown at app/controllers/application_controller.rb:1479

  end

  def require_context_and_read_access
    require_context && authorized_action(@context, @current_user, :read)
  end

  helper_method :clean_return_to

  def require_account_context
    require_context_type(Account)
  end

  def require_course_context
    require_context_type(Course)
  end

  def require_context_type(klass)
    unless require_context && @context.is_a?(klass)
      raise ActiveRecord::RecordNotFound, "Context must be of type '#{klass}'"
    end

    true
  end

  MAX_ACCOUNT_LINEAGE_TO_SHOW_IN_CRUMBS = 3

  # Can be used as a before_action, or just called from controller code.
  # Assigns the variable @context to whatever context the url is scoped
  # to.  So /courses/5/assignments would have a @context=Course.find(5).
  # Also assigns @context_membership to the membership type of @current_user
  # if @current_user is a member of the context.
  def get_context(user_scope: nil)
    GuardRail.activate(:secondary) do
      unless @context
        if params[:course_id]
          course_scope = @token ? Course : Course.active
          @context = api_find(course_scope, params[:course_id])

View on GitHub (pinned to 1c9f0bb801)