{"record":{"id":"088861abae0f76dc","repo":"instructure/canvas-lms","slug":"ineligible-participant","errorCode":null,"errorMessage":"ineligible participant","messagePattern":"ineligible participant","errorType":"exception","errorClass":"CalendarEvent::ReservationError","httpStatus":null,"severity":"error","filePath":"app/models/calendar_event.rb","lineNumber":593,"sourceCode":"  end\n\n  def appointment_group\n    if parent_event.try(:context).is_a?(AppointmentGroup)\n      parent_event.context\n    elsif context_type == \"AppointmentGroup\"\n      context\n    end\n  end\n\n  def account\n    (context_type == \"Account\") ? context : nil\n  end\n\n  class ReservationError < StandardError; end\n\n  def reserve_for(participant, user, options = {})\n    raise ReservationError, \"not an appointment\" unless context_type == \"AppointmentGroup\"\n    raise ReservationError, \"ineligible participant\" unless context.eligible_participant?(participant)\n\n    transaction do\n      lock! # in case two people two participants try to grab the same slot\n      participant.lock! # in case two people try to make a reservation for the same participant\n\n      if options[:cancel_existing]\n        context.reservations_for(participant).lock.each do |reservation|\n          raise ReservationError, \"cannot cancel past reservation\" if reservation.end_at < Time.now.utc\n\n          reservation.updating_user = user\n          reservation.destroy\n        end\n      end\n\n      raise ReservationError, \"participant has met per-participant limit\" if context.max_appointments_per_participant && context.reservations_for(participant).size >= context.max_appointments_per_participant\n      raise ReservationError, \"all slots filled\" if participants_per_appointment && child_events.size >= participants_per_appointment\n      raise ReservationError, \"participant has already reserved this appointment\" if child_events_for(participant).present?\n","sourceCodeStart":575,"sourceCodeEnd":611,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/calendar_event.rb#L575-L611","documentation":"Raised by CalendarEvent#reserve_for when the appointment slot's context reports that the participant is not eligible (AppointmentGroup#eligible_participant?). Even on a valid appointment slot, groups/users outside the appointment group's subcontext (e.g. a specific course section) or not meeting participant-type rules cannot book it.","triggerScenarios":"reserve_for(participant, user) is called with a participant (user or group) that is not part of the appointment group's subcontext — different course/section, participant category mismatch (user vs group appointments), or the participant was removed from the course after the appointment group was created.","commonSituations":"Student tries to book a slot scoped to another course section; group-appointment slot booked directly by a user instead of the group; participant dropped the course; API client passes the current user as participant when the appointment group is limited to group categories.","solutions":["Check eligibility first: appointment_group.eligible_participant?(participant) and only reserve when true.","Pass the correct participant type — for group-signup appointment groups pass the Group, not the User.","Verify the participant is enrolled in the appointment group's subcontext (course/section) and re-sync enrollments if stale.","Update the appointment group's subcontext/participant visibility if the intended audience should be allowed to book."],"exampleFix":"# before\nslot.reserve_for(current_user, current_user)\n# after (group-appointment case)\nparticipant = appointment_group.participant_type == \"Group\" ? current_user.groups.find { |g| appointment_group.eligible_participant?(g) } : current_user\nslot.reserve_for(participant, current_user) if appointment_group.eligible_participant?(participant)","handlingStrategy":"validation","validationCode":"appointment_group = slot.context\nraise ReservationError, \"ineligible participant\" unless appointment_group.eligible_participant?(participant)","typeGuard":null,"tryCatchPattern":"begin\n  slot.reserve_for(participant, user)\nrescue CalendarEvent::ReservationError => e\n  render json: { error: e.message }, status: :forbidden\nend","preventionTips":["Call eligible_participant? before reserving","Pass a Group for group-signup appointment groups, a User otherwise","Keep enrollment data in sync so subcontext membership is current","Communicate slot-visibility rules to API consumers"],"tags":["calendar","appointments","permissions","api"],"backgroundTag":"permission-denied","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}