{"record":{"id":"0fc195682c37d14a","repo":"instructure/canvas-lms","slug":"can-t-accept","errorCode":null,"errorMessage":"can't accept","messagePattern":"can't accept","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/models/enrollment.rb","lineNumber":751,"sourceCode":"    STATE_RANK_HASH[state.to_s]\n  end\n\n  STATE_BY_DATE_RANK = [\"active\", %w[invited creation_pending pending_active pending_invited], \"completed\", \"inactive\", \"rejected\", \"deleted\"].freeze\n  STATE_BY_DATE_RANK_HASH = rank_hash(STATE_BY_DATE_RANK)\n  def self.state_by_date_rank_sql\n    @state_by_date_rank_sql ||= Arel.sql(\n      rank_sql(STATE_BY_DATE_RANK, \"enrollment_states.state\")\n        .sub(/^CASE/, \"CASE WHEN enrollment_states.restricted_access THEN #{STATE_BY_DATE_RANK.index(\"inactive\")}\") # pretend restricted access is the same as inactive\n    )\n  end\n\n  def state_with_date_sortable\n    STATE_RANK_HASH[state_based_on_date.to_s]\n  end\n\n  def accept!\n    res = accept\n    raise \"can't accept\" unless res\n\n    res\n  end\n\n  def accept(force: false)\n    GuardRail.activate(:primary) do\n      return false unless force || invited?\n\n      if update_attribute(:workflow_state, \"active\")\n        if type == \"StudentEnrollment\"\n          Enrollment.recompute_final_score_in_singleton(user_id, course_id)\n        end\n        true\n      end\n    end\n  end\n\n  def reset_notifications_cache","sourceCodeStart":733,"sourceCodeEnd":769,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/enrollment.rb#L733-L769","documentation":"Enrollment#accept! is the strict/banging wrapper around #accept: it calls accept and raises 'can't accept' when accept returns a falsy result, i.e. the enrollment could not be transitioned to an accepted/invited-accepted state under current rules (wrong workflow state, enrollment not invitable/accept-able, etc.).","triggerScenarios":"Calling enrollment.accept! when #accept returns false/nil — typically because the enrollment's workflow_state does not permit acceptance (e.g. already rejected/completed/deleted, not invited), or the user on the enrollment is not the one being accepted for without force.","commonSituations":"Self-enrollment or invitation-accept flows hitting a stale enrollment that was already rejected or deleted in another tab; background jobs accepting enrollments after the course concluded; specs accepting a factory-built enrollment that was never in invited state.","solutions":["Check the enrollment state first and only call accept! when acceptance is allowed (e.g. invited?/invited_state?)","Use the non-banging enrollment.accept and handle the false return instead of raising","Reload the enrollment before accepting to pick up concurrent state changes","Pass accept(force: true) via the underlying method only when you intentionally need to override constraints"],"exampleFix":"// before\nenrollment.accept!\n// after\nenrollment.reload\nenrollment.accept if enrollment.invited?","handlingStrategy":"try-catch","validationCode":"enrollment.reload\nraise 'not acceptable' unless enrollment.may_accept? # or check invited state\nenrollment.accept!","typeGuard":null,"tryCatchPattern":"begin\n  enrollment.accept!\nrescue RuntimeError => e\n  raise unless e.message == \"can't accept\"\n  Rails.logger.warn(\"enrollment #{enrollment.id} not acceptable (#{enrollment.workflow_state})\")\nend","preventionTips":["Check workflow_state allows acceptance before calling accept!","Reload before mutating to avoid stale state","Prefer non-banging accept and handle its boolean return in jobs"],"tags":["ruby","rails","enrollment","state-machine","invitation"],"backgroundTag":"invalid-state-transition","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"}