{"record":{"id":"1541139ed307e49c","repo":"instructure/canvas-lms","slug":"user-user-id-doesn-t-have-an-enrollment-in-the-course-of-is","errorCode":null,"errorMessage":"User #{user_id} doesn't have an enrollment in the course of #{is_tags ? \"differentiation tag\" : \"group\"} #{group_id}.","messagePattern":"User #(.+?) doesn't have an enrollment in the course of #(.+?) #(.+?)\\.","errorType":"exception","errorClass":"ImportError","httpStatus":null,"severity":"error","filePath":"lib/sis/group_membership_importer.rb","lineNumber":63,"sourceCode":"        group_id = group_id.to_s\n        raise ImportError, \"No #{is_tags ? \"tag_id\" : \"group_id\"} given for a #{is_tags ? \"differentiation tag\" : \"group\"} user\" if group_id.blank?\n        raise ImportError, \"No user_id given for a #{is_tags ? \"differentiation tag\" : \"group\"} user\" if user_id.blank?\n        raise ImportError, \"Improper status \\\"#{status}\\\" for a #{is_tags ? \"differentiation tag\" : \"group\"} user\" unless /\\A(accepted|deleted)/i.match?(status)\n        return if @batch.skip_deletes? && status =~ /deleted/i\n\n        pseudo = @root_account.pseudonyms.find_by(sis_user_id: user_id)\n        user = pseudo&.user\n\n        group = @groups_cache[group_id]\n        scope = is_tags ? @root_account.all_differentiation_tags : @root_account.all_groups\n        group ||= scope.where(sis_source_id: group_id).preload(:context).take\n        @groups_cache[group.sis_source_id] = group if group\n\n        raise ImportError, \"User #{user_id} didn't exist for #{is_tags ? \"differentiation tag\" : \"group\"} user\" unless user\n        raise ImportError, \"#{is_tags ? \"Differentiation tag\" : \"Group\"} #{group_id} didn't exist for #{is_tags ? \"differentiation tag\" : \"group\"} user\" unless group\n\n        if group.context.is_a?(Course) && !group.context.all_real_users.where(id: user.id).exists?\n          raise ImportError, \"User #{user_id} doesn't have an enrollment in the course of #{is_tags ? \"differentiation tag\" : \"group\"} #{group_id}.\"\n        end\n\n        if group && is_tags\n          raise ImportError, \"Differentiation Tags are not enabled for Account #{group.context.account.id}.\" unless group.context.account.allow_assign_to_differentiation_tags?\n        end\n\n        # can't query group.group_memberships, since that excludes deleted memberships\n        group_membership = GroupMembership.where(group_id: group, user_id: user)\n                                          .order(Arel.sql(\"CASE WHEN workflow_state = 'accepted' THEN 0 ELSE 1 END\")).take\n        group_membership ||= group.group_memberships.build(user:)\n\n        group_membership.sis_batch_id = @batch.id\n\n        case status\n        when /accepted/i\n          group_membership.workflow_state = \"accepted\"\n        when /deleted/i\n          group_membership.workflow_state = \"deleted\"","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/sis/group_membership_importer.rb#L45-L81","documentation":"When the group/tag's context is a Course, the importer verifies the user has an enrollment in that course (all_real_users) before attaching membership. A user without an enrollment raises ImportError, since differentiation tags and course groups only make sense for enrolled users.","triggerScenarios":"Membership row pairs a user with a course-scoped group/tag whose course the user is not (or no longer) enrolled in; user's enrollment was deleted before this import; group moved to a different course after memberships were authored.","commonSituations":"Enrollments CSV processed after group memberships; user dropped the course between export and import; tag assigned to a course section user never enrolled in; sis batch ordering problems.","solutions":["Import/create the user's course enrollment before the group membership row","Remove the membership row for users not enrolled in the course","Verify enrollment sis data is up to date and the user is a 'real' (non-deleted, non-temp) user","Re-run the import after enrollments have been processed"],"exampleFix":"# before (no enrollment row for u_100 in C123)\nu_100,tag_C123_1,accepted\n# after\nfirst import enrollments.csv with u_100 in C123, then the membership row","handlingStrategy":"validation","validationCode":"if group.context.is_a?(Course) && !group.context.all_real_users.where(id: user.id).exists?\n  raise \"user #{user.id} lacks enrollment in #{group.context.id}\"\nend","typeGuard":null,"tryCatchPattern":"begin\n  importer.add_group_membership(uid, gid, status)\nrescue SIS::GroupImporter::ImportError => e\n  raise unless e.message =~ /doesn't have an enrollment in the course/\n  Rails.logger.warn(\"User #{uid} not enrolled; deferring row until enrollments import\")\nend","preventionTips":["Import enrollments before group memberships in every batch","Reconcile drops: remove membership rows for users who un-enrolled","Verify enrollment data is current as of the import date"],"tags":["sis-import","groups","enrollment","prerequisite"],"backgroundTag":"entity-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"}