instructure/canvas-lms · error · ParseError

Invalid fields after ratings

Error message

Invalid fields after ratings: %{fields}

What it means

Raised by Outcomes::CsvImporter#validate_headers when the header row contains non-empty, non-nil values in the ratings* columns that are not the literal 'ratings' column position terminator — i.e. content appears immediately after the 'ratings' header that the importer cannot interpret. The importer treats everything from 'ratings' onward as rating pairs and requires nothing extra after the ratings block.

Solutions

  1. Remove any column headers/cells after the 'ratings' column; only rating points/description pairs belong there.
  2. Regenerate the CSV from the official Canvas outcome import template.
  3. Verify the header row exactly: required fields first, then a single 'ratings' column containing point/description pairs.

Example fix

// before
vendor_guid,title,ratings,notes
// after
vendor_guid,title,ratings
Defensive patterns

Strategy: validation

Validate before calling

headers = CSV.open(path, &:first)
idx = headers.index('ratings')
extra = headers[(idx + 1)..].to_a.compact_blank
raise "unexpected fields after ratings: #{extra}" unless extra.empty?

Try / catch

begin
  importer.parse_file
rescue Outcomes::CsvImporter::ParseError => e
  notify_user("Header row problem: #{e.message}")
end

Prevention

When it happens

Trigger: A CSV header like `vendor_guid,title,description,ratings,extra_column` where 'extra_column' follows 'ratings' and its pairs; stray trailing values after the last rating description; commas at the end of the header row creating empty-but-shifted cells the importer interprets as extra fields.

Common situations: Hand-edited CSVs adding a custom column after the ratings section; exporting from another tool that appends columns; template misuse where rating pairs are misaligned so a description lands in the wrong slot.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at lib/outcomes/csv_importer.rb:143

      @file.rewind
      count
    end

    def check_encoding(str)
      encoded = str&.force_encoding("utf-8")
      valid = (encoded || "").valid_encoding?
      raise ParseError, I18n.t("Not a valid utf-8 string: %{string}", string: str.inspect) unless valid

      encoded
    end

    def validate_headers(row, _index)
      main_columns_end = row.find_index("ratings") || row.length
      headers = row.slice(0, main_columns_end).map(&:to_sym)

      after_ratings = row[(main_columns_end + 1)..] || []
      after_ratings = after_ratings.compact_blank.map(&:to_s)
      raise ParseError, I18n.t("Invalid fields after ratings: %{fields}", fields: after_ratings.inspect) unless after_ratings.empty?

      missing = (REQUIRED_FIELDS - headers).map(&:to_s)
      raise ParseError, I18n.t("Missing required fields: %{fields}", fields: missing.inspect) unless missing.empty?

      invalid = (headers - OPTIONAL_FIELDS - REQUIRED_FIELDS).map(&:to_s)
      raise ParseError, I18n.t("Invalid fields: %{fields}", fields: invalid.inspect) unless invalid.empty?

      headers
    end

    def import_row(headers, row)
      simple = headers.zip(row).to_h
      ratings = row[headers.length..]

      object = simple.to_h
      object[:ratings] = parse_ratings(ratings)
      object[:learning_outcome_group_id] = @import[:learning_outcome_group_id]
      if object[:mastery_points].present?

View on GitHub (pinned to 1c9f0bb801)