instructure/canvas-lms · error · GraphQL::ExecutionError
Enrollment is not in invited state
Error message
Enrollment is not in invited state
What it means
The mutation only accepts invitations in the 'invited' workflow_state; otherwise it raises 'Enrollment is not in invited state'. Accepting is a state transition, so enrollments already accepted, declined, rejected, completed, or deleted cannot pass through accept! again.
Solutions
- Check the enrollment's current workflow_state before calling (or fetch the enrollment via GraphQL/API) and treat already-accepted as success.
- Make the caller idempotent: persist locally that the invitation was accepted and skip repeat calls.
- If the invitation was declined/expired, re-invite the user to generate a new enrollment.
- Investigate enrollment state transitions (concluded course, deleted enrollment) if the state changed unexpectedly.
- On the server side you could accept or return success for already-accepted enrollments instead of erroring, for idempotency.
Example fix
// before
if (!enrollment.invited?) raise GraphQL::ExecutionError, "Enrollment is not in invited state"
// after
if (enrollment.accepted?) return { enrollment:, success: true } # idempotent
raise GraphQL::ExecutionError, "Enrollment is not in invited state" unless enrollment.invited? Defensive patterns
Strategy: fallback
Validate before calling
// make the call idempotent: check state first, skip if already accepted
if (locallyAccepted.has(enrollmentUuid)) return { success: true } Try / catch
try {
return await client.request(ACCEPT_INVITATION, { enrollmentUuid })
} catch (e) {
if (/not in invited state/i.test(e.message))
return { success: true, alreadyHandled: true } // treat as done
throw e
} Prevention
- Disable the accept button after first success
- Don't retry non-idempotent mutations on timeout without checking state
- Re-invite instead of re-accepting declined/expired invitations
- Persist accepted uuids locally for sync jobs
When it happens
Trigger: Double-clicking accept or retrying the mutation after a first successful accept; calling it on a previously declined invitation; course concluded/invitation expired so the enrollment moved out of 'invited'; batch scripts re-running on already-processed enrollments.
Common situations: Users replaying invitation links from email after already accepting; automated sync jobs lacking idempotency; race conditions where two tabs accept simultaneously and the second sees a non-invited state.
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
- can't accept
- Enrollment invitation not found
- Enrollment is not in invited state
- no drafts found
- Unauthorized
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/0100a2e2027991e1.
Report an issue: GitHub.
Appendix: source
Thrown at app/graphql/mutations/accept_enrollment_invitation.rb:39
module Mutations
class AcceptEnrollmentInvitation < 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.accept!
{
enrollment:,
success: true
}
else
{
enrollment: nil,
success: false,
errors: [{ message: I18n.t("Failed to accept enrollment invitation") }]
}
end
rescue => e
{
enrollment: nil,
success: false,View on GitHub (pinned to 1c9f0bb801)