instructure/canvas-lms · error · ImportError

Cannot restore course #

Error message

Cannot restore course #{course_id} because the associated account #{course.account.sis_source_id} is deleted

What it means

SisCourseImporter#add_course raises ImportError when the course's associated account has been deleted (course.account&.deleted?). This blocks restoring/undeleting a course into a dead account, since a course cannot live under a deleted account. The message includes the offending account's sis_source_id.

Solutions

  1. Re-activate (restore) the deleted account in accounts.csv (status: active) before re-importing the course
  2. Point the course at a live account by changing its account_id in courses.csv — note this requires account_id not be stuck; clear stuck sis fields if needed
  3. Move the course to the root account or another active subaccount in the source SIS export
  4. If the account deletion was a mistake, undo it in Canvas (rails console) before rerunning the batch

Example fix

// before (accounts.csv): the course's account stays deleted while course row says active
"acc-eng","Engineering",deleted
// after
"acc-eng","Engineering",active
Defensive patterns

Strategy: validation

Validate before calling

acct = course.account
if course.persisted? && acct&.deleted?
  raise ArgumentError, "course #{course.sis_source_id} points at deleted account #{acct.sis_source_id}"
end

Type guard

def restorable_account?(course) = course.account.nil? || course.account.active?

Try / catch

begin
  importer.add_course(...)
rescue SisImport::ImportError => e
  raise unless e.message.start_with?('Cannot restore course')
  log.error("restore blocked by deleted account; restore the account first: #{e.message}")
end

Prevention

When it happens

Trigger: Re-importing (restoring) a previously deleted course whose account_id is stuck (stuck_sis_fields includes :account_id, so account reassignment is skipped) while that stored account is now deleted. Also triggered when the resolved/matched account was soft-deleted between imports.

Common situations: Account was deleted in a prior SIS run but course rows referencing it came back; the course row is processed for restore (status active/published) while its account is gone; workflow_state transitions on the account outside SIS control.

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

Appendix: source

Thrown at lib/sis/course_importer.rb:105

            term = @root_account.enrollment_terms.active.find_by(sis_source_id: term_id)
          end
          course.enrollment_term = term if term
          course.root_account = @root_account

          account = nil
          account = @root_account.all_accounts.find_by(sis_source_id: account_id) if account_id.present?
          account ||= @root_account.all_accounts.find_by(sis_source_id: fallback_account_id) if fallback_account_id.present?
          if account_id.present? && !account
            raise ImportError, "Account not found \"#{account_id}\" for course #{course_id}"
          end

          course_account_stuck = course.stuck_sis_fields.include?(:account_id)
          if !course_account_stuck && account&.active?
            course.account = account
          end

          if course.account&.deleted?
            raise ImportError, "Cannot restore course #{course_id} because the associated account #{course.account.sis_source_id} is deleted"
          end

          course.account ||= @root_account

          update_account_associations = course.account_id_changed? || course.root_account_id_changed?

          if account&.enable_as_k5_account? && friendly_name.present?
            course.friendly_name = friendly_name
          end

          course.integration_id = integration_id if integration_id.present?
          course.sis_source_id = course_id
          active_state = status.casecmp?("published") ? "available" : "claimed"
          unless course.stuck_sis_fields.include?(:workflow_state)
            if %w[active unpublished published].include?(status.downcase)
              case course.workflow_state
              when "completed"
                # not using active state here, because it has always been set to available

View on GitHub (pinned to 1c9f0bb801)