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
- Resolve a real User first and check presence before calling claim_with_teacher
- Fall back to the current user or an admin as the claiming teacher when appropriate
- 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
- Verify User lookup results before claiming courses
- Check course workflow_state is :created (otherwise the call silently no-ops)
- Pass explicit user objects, not optional params
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
- DONT USE THIS, use .short_name instead
- Parent assignment Course override not found for course
- assessor and assessee required
- association required
- can only forward one conversation at a time
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
endView on GitHub (pinned to 1c9f0bb801)