instructure/canvas-lms · error · InvalidDataError

Outcome " " has already appeared in this import

Error message

Outcome "%{guid}" has already appeared in this import

What it means

Raised when import_outcome finds a prior outcome (by vendor_guid) whose outcome_import_id already equals the current import's id, meaning the same vendor_guid row has been processed twice within one outcome import. The library treats duplicate rows in a single import as data corruption, since a guid must uniquely identify an outcome per import run.

Solutions

  1. Deduplicate the import file so each vendor_guid appears only once.
  2. If both rows are intentional updates, merge their fields into one row keyed by the single vendor_guid.
  3. Fix the generating export/script that emitted the duplicate guid.

Example fix

// before: rows [{vendor_guid:'A',...},{vendor_guid:'A',...}]
// after: merge duplicates
{
  "vendor_guid": "A",
  "title": "merged title",
  "ratings": [/* merged ratings */]
}
Defensive patterns

Strategy: validation

Validate before calling

// Ruby: dedupe rows by vendor_guid before upload
guids = rows.map { |r| r[:vendor_guid] }
raise 'duplicate guids' if guids.uniq.size != guids.size
rows.uniq_by { |r| r[:vendor_guid] }

Try / catch

begin
  importer.import_object(...)
rescue Outcomes::Import::InvalidDataError => e
  raise unless e.message.include?('already appeared in this import')
  # drop later duplicate rows and re-queue the import
end

Prevention

When it happens

Trigger: An outcome import file (CSV/JSON) contains two rows with the same vendor_guid; import_outcome checks model.outcome_import_id == outcome_import_id at lib/outcomes/import.rb:167.

Common situations: Manually merged or concatenated outcome export files; CSV rows duplicated by a copy/paste mistake; a script that retries rows without deduplicating before upload.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/29073adb96faafc9. Report an issue: GitHub.

Appendix: source

Thrown at lib/outcomes/import.rb:167

        )
      end

      model = find_prior_outcome(outcome)
      model.context = context if model.new_record?

      unless child_context?(context, of: model.context)
        raise InvalidDataError, I18n.t(
          "Outcome with ID %{guid} already exists in another unrelated course or account (%{name})",
          guid: outcome[:vendor_guid],
          name: model.context.name
        )
      end

      allow_indirect = outcome.key?(:course_id)
      parents = find_parents(outcome, context, allow_indirect:)

      if model.outcome_import_id == outcome_import_id
        raise InvalidDataError, I18n.t(
          'Outcome "%{guid}" has already appeared in this import',
          guid: outcome[:vendor_guid]
        )
      end

      model.vendor_guid = outcome[:vendor_guid]
      model.title = outcome[:title]
      model.description = infer_nil_value(model, :description, outcome)
      model.display_name = infer_nil_value(model, :display_name, outcome)
      model.calculation_method = outcome[:calculation_method].presence || model.default_calculation_method
      model.calculation_int = outcome[:calculation_int].presence || model.default_calculation_int
      # let removing the outcome_links content tags delete the underlying outcome
      model.workflow_state = "active" unless outcome[:workflow_state] == "deleted"

      prior_rubric = model.rubric_criterion || {}
      changed = ->(k) { outcome[k].present? && outcome[k] != prior_rubric[k] }
      rubric_change = changed.call(:ratings) || changed.call(:mastery_points)
      model.rubric_criterion = create_rubric(outcome[:ratings], outcome[:mastery_points]) if rubric_change

View on GitHub (pinned to 1c9f0bb801)