instructure/canvas-lms · error · ActiveRecord::RecordNotSaved

Cannot unarchive a deleted LearningOutcome

Error message

Cannot unarchive a deleted LearningOutcome

What it means

LearningOutcome#unarchive! returns an archived outcome to 'active' and clears archived_at. Unarchiving a deleted outcome is an invalid transition, raising ActiveRecord::RecordNotSaved when workflow_state is 'deleted'.

Solutions

  1. Guard: only call unarchive! when workflow_state == 'archived'.
  2. Reload the outcome and re-check state before the transition.
  3. Rescue ActiveRecord::RecordNotSaved and surface 'outcome was deleted' to the user.
  4. Restore deleted outcomes via the proper deletion-undo path, not unarchive!.

Example fix

// before
outcome.unarchive!
// after
outcome.reload.unarchive! if outcome.reload.workflow_state == "archived"
Defensive patterns

Strategy: try-catch

Validate before calling

outcome.reload.unarchive! if outcome.reload.workflow_state == "archived"

Try / catch

begin
  outcome.unarchive!
rescue ActiveRecord::RecordNotSaved
  flash[:error] = "This outcome was deleted and cannot be unarchived"
end

Prevention

When it happens

Trigger: Calling outcome.unarchive! on a deleted LearningOutcome — e.g. a stale UI tab or a job operating on a record deleted after load.

Common situations: Outcome deleted while archived; user clicks restore from a stale list; importers toggling states without reloading.

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/a29c70967e2a71f7. Report an issue: GitHub.

Appendix: source

Thrown at app/models/learning_outcome.rb:423

  def archive!
    # Only active outcomes can be archived
    if workflow_state == "active"
      self.workflow_state = "archived"
      self.archived_at = Time.now.utc
      save!
    elsif workflow_state == "deleted"
      raise ActiveRecord::RecordNotSaved, "Cannot archive a deleted LearningOutcome"
    end
  end

  def unarchive!
    # Only archived outcomes can be unarchived
    if workflow_state == "archived"
      self.workflow_state = "active"
      self.archived_at = nil
      save!
    elsif workflow_state == "deleted"
      raise ActiveRecord::RecordNotSaved, "Cannot unarchive a deleted LearningOutcome"
    end
  end

  def assessed?(course = nil)
    if course
      learning_outcome_results.active.where(context_id: course, context_type: "Course").exists?
    elsif learning_outcome_results.active.loaded?
      learning_outcome_results.active.any?
    else
      learning_outcome_results.active.exists?
    end
  end

  def tie_to(context)
    @tied_context = context
  end

  def mastery_points

View on GitHub (pinned to 1c9f0bb801)