instructure/canvas-lms · error · GraphQL::ExecutionError

Enrollment is not in invited state

Error message

Enrollment is not in invited state

What it means

Raised when the enrollment exists and belongs to the user but its workflow_state is not 'invited' (enrollment.invited? is false). Only pending invitations can be rejected; already-accepted, completed, rejected, or deleted enrollments are invalid targets.

Solutions

  1. Check enrollment.workflow_state == 'invited' before calling; surface a friendly message otherwise
  2. Disable the reject action in the UI after the first successful call
  3. If already accepted, instruct the user to leave the course instead of rejecting
  4. Handle idempotency: treat 'already rejected' outcomes as success in client logic

Example fix

// before
// second click after rejection
rejectEnrollmentInvitation(uuid)
// after
if (enrollment.workflowState === 'invited') {
  await rejectEnrollmentInvitation(uuid)
} else {
  showMessage('This invitation has already been processed.')
}
Defensive patterns

Strategy: validation

Validate before calling

if (enrollment.workflowState !== 'invited') {
  show('This invitation has already been ' + enrollment.workflowState)
  return
}

Type guard

function isInvitable(e) { return e?.workflowState === 'invited' }

Try / catch

try {
  await rejectEnrollmentInvitation({ enrollmentUuid })
} catch (e) {
  if (e.message.includes('not in invited state')) { refetchEnrollment(); show('Invitation already processed') }
  else throw e
}

Prevention

When it happens

Trigger: Double-clicking the reject action so the second call hits an already-rejected enrollment; rejecting after the user already accepted the course; enrollment auto-accepted due to a prior interaction; enrollment hard/soft deleted by an admin in the meantime.

Common situations: Users clicking a stale 'decline invitation' email link after previously accepting; UI not refreshing state after the first rejection; race between two browser tabs.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at app/graphql/mutations/reject_enrollment_invitation.rb:39

module Mutations
  class RejectEnrollmentInvitation < BaseMutation
    argument :enrollment_uuid, String, required: true

    field :enrollment, Types::EnrollmentType, null: true
    field :success, Boolean, null: false

    def resolve(input:, **)
      user = context[:current_user]
      raise GraphQL::ExecutionError, I18n.t("Must be logged in") unless user

      enrollment = Enrollment.where(uuid: input[:enrollment_uuid]).first
      raise GraphQL::ExecutionError, I18n.t("Enrollment invitation not found") unless enrollment

      # Verify the enrollment belongs to the current user
      raise GraphQL::ExecutionError, I18n.t("Unauthorized") unless enrollment.user == user

      # Verify the enrollment is in invited state
      raise GraphQL::ExecutionError, I18n.t("Enrollment is not in invited state") unless enrollment.invited?

      begin
        if enrollment.reject
          {
            enrollment:,
            success: true
          }
        else
          {
            enrollment: nil,
            success: false,
            errors: [{ message: I18n.t("Failed to reject enrollment invitation") }]
          }
        end
      rescue => e
        {
          enrollment: nil,
          success: false,

View on GitHub (pinned to 1c9f0bb801)