{"record":{"id":"899145d2d2b22866","repo":"instructure/canvas-lms","slug":"enrollment-invitation-not-found","errorCode":null,"errorMessage":"Enrollment invitation not found","messagePattern":"Enrollment invitation not found","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"error","filePath":"app/graphql/mutations/accept_enrollment_invitation.rb","lineNumber":33,"sourceCode":"# details.\n#\n# You should have received a copy of the GNU Affero General Public License along\n# with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\nmodule Mutations\n  class AcceptEnrollmentInvitation < BaseMutation\n    argument :enrollment_uuid, String, required: true\n\n    field :enrollment, Types::EnrollmentType, null: true\n    field :success, Boolean, null: false\n\n    def resolve(input:, **)\n      user = context[:current_user]\n      raise GraphQL::ExecutionError, I18n.t(\"Must be logged in\") unless user\n\n      enrollment = Enrollment.where(uuid: input[:enrollment_uuid]).first\n      raise GraphQL::ExecutionError, I18n.t(\"Enrollment invitation not found\") unless enrollment\n\n      # Verify the enrollment belongs to the current user\n      raise GraphQL::ExecutionError, I18n.t(\"Unauthorized\") unless enrollment.user == user\n\n      # Verify the enrollment is in invited state\n      raise GraphQL::ExecutionError, I18n.t(\"Enrollment is not in invited state\") unless enrollment.invited?\n\n      begin\n        if enrollment.accept!\n          {\n            enrollment:,\n            success: true\n          }\n        else\n          {\n            enrollment: nil,\n            success: false,\n            errors: [{ message: I18n.t(\"Failed to accept enrollment invitation\") }]","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/accept_enrollment_invitation.rb#L15-L51","documentation":"AcceptEnrollmentInvitation looks up the enrollment by the provided enrollment_uuid (Enrollment.where(uuid: input[:enrollment_uuid]).first) and raises 'Enrollment invitation not found' when no enrollment matches. The uuid is a per-invitation identifier, so any typo, stale link, or deleted/merged enrollment leads here.","triggerScenarios":"Mutation called with an enrollment_uuid that does not exist: truncated/copy-paste-damaged uuid from an invitation email, invitation revoked and enrollment deleted, uuid from a different shard/ENV, or a fabricated id.","commonSituations":"Users clicking old invitation emails after the course was concluded and enrollments purged; test fixtures reusing uuids across environments (test vs production DB); self-registration tools storing uuids before enrollment creation completes.","solutions":["Verify the exact enrollment_uuid string from the invitation (no truncation/whitespace) and retry.","Re-issue the invitation so a fresh enrollment/uuid is generated, then accept that.","Confirm you are hitting the same environment/shard where the enrollment was created.","Check the enrollment still exists: find by uuid in the Rails console (Enrollment.find_by(uuid: ...)).","Handle nil in client code and surface 'invitation no longer valid' to the user with a path to re-invite."],"exampleFix":"// before\nconst enrollment = await Enrollment.findOne({ where: { uuid: suppliedUuid } })\nif (!enrollment) throw new Error(\"Enrollment invitation not found\")\n// after\nconst enrollment = await Enrollment.findOne({ where: { uuid: normalizeUuid(suppliedUuid) } })\nif (!enrollment) return { success: false, reason: \"invitation_invalid_or_expired\" } // re-invite flow","handlingStrategy":"validation","validationCode":"// validate uuid shape and presence before the call\nconst UUID_RE = /^[0-9a-f]{32}$/i // Canvas uuids (adjust per format)\nif (!UUID_RE.test((enrollmentUuid || \"\").trim()))\n  throw new Error(\"invalid enrollment_uuid; copy it from the invitation link exactly\")","typeGuard":null,"tryCatchPattern":"try {\n  await client.request(ACCEPT_INVITATION, { enrollmentUuid })\n} catch (e) {\n  if (/Enrollment invitation not found/.test(e.message))\n    return showInvitationExpiredScreen() // offer re-invite\n  throw e\n}","preventionTips":["Copy uuids verbatim from invitation links (no truncation)","Don't reuse uuids across environments","Handle revoked/expired invitations with a re-invite path","Verify enrollment existence in console when debugging"],"tags":["graphql","mutation","enrollment","not-found"],"backgroundTag":"record-not-found","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"}