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
- Re-activate (restore) the deleted account in accounts.csv (status: active) before re-importing the course
- 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
- Move the course to the root account or another active subaccount in the source SIS export
- 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
- Restore or re-parent accounts before re-importing courses that reference them
- Audit for deleted accounts still referenced by active course rows each cycle
- Avoid deleting accounts that still have courses; move courses first
- Clear stuck sis_fields on account_id when you intentionally reassign a course's account
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
- Cannot move # # because it has # _memberships.
- A course did not pass validation
- A # user did not pass validation (user: # , # : # , error…
- A (templated) course did not pass validation
- A # did not pass validation (# : # , error: # )
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 availableView on GitHub (pinned to 1c9f0bb801)