instructure/canvas-lms · error · ImportError

Cannot delete the sub_account with ID: #

Error message

Cannot delete the sub_account with ID: #{account_id} because it has active courses.

What it means

Similar to the sub-accounts guard: deleting an account via SIS is blocked with ImportError when account.courses.active.exists? — the account still contains active courses. Courses must be deleted/concluded before their account can be deleted.

Solutions

  1. Delete or conclude all active courses in the account (via SIS courses.csv rows with status 'deleted' or the UI) before deleting the account.
  2. Move active courses to a different account, then delete.
  3. Verify with a console check: account.courses.active.exists?

Example fix

// before
# course still active
add_account('dept1', 'root', 'deleted', 'Department', nil)
// after
# courses.csv: mark course deleted first, then
add_account('dept1', 'root', 'deleted', 'Department', nil)
Defensive patterns

Strategy: try-catch

Validate before calling

acct = root_account.all_accounts.find_by(sis_source_id: account_id)
raise "delete/complete courses first" if acct&.courses&.active&.exists?

Try / catch

begin
  importer.add_account(id, parent_id, 'deleted', name, integration_id)
rescue SIS::BaseImporter::ImportError => e
  logger.warn("blocked: #{e.message}")
end

Prevention

When it happens

Trigger: add_account with status 'deleted' for an account that has at least one active course under it.

Common situations: Retiring an academic department account whose courses are still unpublished/active; SIS sync not including course deletions for that subtree.

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

Appendix: source

Thrown at lib/sis/account_importer.rb:91

        account.root_account = @root_account
        if account.new_record? || !account.stuck_sis_fields.include?(:parent_account_id) || Account.sis_stickiness_options[:add_sis_stickiness]
          account.parent_account = parent || @root_account
        end

        # only update the name on new records, and ones that haven't been changed since the last sis import
        account.name = name if name.present? && (account.new_record? || !account.stuck_sis_fields.include?(:name))

        account.integration_id = integration_id if integration_id.present?
        account.sis_source_id = account_id

        if status.present?
          case status
          when /active/i
            account.workflow_state = "active"
          when /deleted/i
            raise ImportError, "Cannot delete the sub_account with ID: #{account_id} because it has active sub accounts." if account.sub_accounts.active.exists?
            raise ImportError, "Cannot delete the sub_account with ID: #{account_id} because it has active courses." if account.courses.active.exists?

            account.workflow_state = "deleted"
          end
        end

        @accounts_cache[account.sis_source_id] = account

        unless account.changed?
          @success_count += 1
          accounts_to_set_sis_batch_ids << account.id unless account.sis_batch_id == @batch.try(:id)
          return
        end

        account.sis_batch_id = @batch.id

        update_account_associations = account.root_account_id_changed? || account.parent_account_id_changed?
        if account.save
          data = SisBatchRollBackData.build_data(sis_batch: @batch, context: account)

View on GitHub (pinned to 1c9f0bb801)