instructure/canvas-lms · error

Must provide a valid teacher

Error message

Must provide a valid teacher

What it means

Guard in Course (teacher enrollment path, course.rb ~1994): when adding a teacher to a course, the supplied user either isn't found or isn't enrollable as a teacher, so 'Must provide a valid teacher' is raised instead of creating an invalid teacher enrollment.

Solutions

  1. Resolve a real User first and check presence before calling claim_with_teacher
  2. Fall back to the current user or an admin as the claiming teacher when appropriate
  3. Note claim_with_teacher is a no-op (returns nil) if state != :created, so also verify state

Example fix

// before
course.claim_with_teacher(User.find_by(email: email))
// after
teacher = User.find_by(email: email)
raise 'teacher not found' unless teacher
course.claim_with_teacher(teacher)
Defensive patterns

Strategy: validation

Validate before calling

raise 'teacher required' unless teacher.is_a?(User)
return unless course.state == :created
course.claim_with_teacher(teacher)

Type guard

def valid_teacher(u)
  u.is_a?(User) && !u.new_record?
end

Try / catch

begin
  course.claim_with_teacher(teacher)
rescue RuntimeError => e
  raise unless e.message == 'Must provide a valid teacher'
  # resolve a fallback teacher or abort
end

Prevention

When it happens

Trigger: Calling course.claim_with_teacher(nil) or with an expression that evaluated to nil (e.g. User.find_by that found nothing) for a course whose state is :created.

Common situations: Console/admin scripts claiming soft-concluded or self-enrollment courses with a lookup that returned nil; API code passing an optional user param straight through.

Related errors


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

Appendix: source

Thrown at app/models/course.rb:1994

      SisBatchRollBackData.bulk_insert_roll_back_data(data) if data
      Enrollment.where(id: e_batch.map(&:id)).update_all(workflow_state: "deleted", updated_at: Time.zone.now, archived_at:)
      EnrollmentState.where(enrollment_id: e_batch.map(&:id))
                     .update_all(["state = ?, state_is_current = ?, lock_version = lock_version + 1, updated_at = ?", "deleted", true, Time.now.utc])
      User.touch_and_clear_cache_keys(user_ids, :enrollments)
      User.delay_if_production.update_account_associations(user_ids) if user_ids.any?
    end
    c_data = SisBatchRollBackData.build_dependent_data(sis_batch:, contexts: courses, updated_state: "deleted", batch_mode_delete: batch_mode)
    SisBatchRollBackData.bulk_insert_roll_back_data(c_data) if c_data
    Course.where(id: courses).update_all(workflow_state: "deleted", updated_at: Time.zone.now, archived_at:)
    courses.count
  end

  def call_event(event)
    send(event) if current_state.events.include? event.to_sym
  end

  def claim_with_teacher(user)
    raise "Must provide a valid teacher" unless user
    return unless state == :created

    e = enroll_user(user, "TeacherEnrollment", enrollment_state: "active") # teacher(user)
    claim
    e
  end

  def self.require_assignment_groups(contexts)
    courses = contexts.grep(Course)
    groups = Shard.partition_by_shard(courses) do |shard_courses|
      AssignmentGroup.select("id, context_id, context_type").where(context_type: "Course", context_id: shard_courses)
    end.index_by(&:context_id)
    courses.each do |course|
      unless groups[course.id]
        course.require_assignment_group
      end
    end
  end

View on GitHub (pinned to 1c9f0bb801)