{"record":{"id":"49a2bad33ee1833c","repo":"instructure/canvas-lms","slug":"migration-warning-user-message-opts-inspect","errorCode":null,"errorMessage":"Migration warning: #{user_message}: #{opts.inspect}","messagePattern":"Migration warning: #(.+?): #(.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"app/models/content_migration.rb","lineNumber":321,"sourceCode":"      mi.delete\n    else\n      mi.save!\n    end\n\n    mi\n  end\n\n  def add_todo(user_message, opts = {})\n    add_issue(user_message, :todo, opts)\n  end\n\n  def add_error(user_message, opts = {})\n    level = opts.fetch(:issue_level, :error)\n    add_issue(user_message, level, opts)\n  end\n\n  def add_warning(user_message, opts = {})\n    Rails.logger.warn(\"Migration warning: #{user_message}: #{opts.inspect}\")\n    unless opts.is_a? Hash\n      # convert deprecated behavior to new\n      exception_or_info = opts\n      opts = {}\n      if exception_or_info.is_a?(Exception)\n        opts[:exception] = exception_or_info\n      else\n        opts[:error_message] = exception_or_info\n      end\n    end\n    add_issue(user_message, :warning, opts)\n  end\n\n  def add_unique_warning(key, warning, opts = {})\n    @added_warnings ||= Set.new\n    return if @added_warnings.include?(key) # only add it once\n\n    @added_warnings << key","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/content_migration.rb#L303-L339","documentation":"ContentMigration#add_warning records a non-fatal migration issue on the migration object and logs this warning line. The message interpolates the human-readable problem plus the opts hash, which often carries an exception or failing item info. It surfaces in the migration UI as a warning without stopping the import.","triggerScenarios":"Any migration step (attachment copy, missing content-link detection, importer callbacks) calls add_warning(message, opts) — e.g. an attachment failed to copy, a referenced file is missing, or a deprecated Exception argument was passed as the second parameter.","commonSituations":"Course copies where source files were deleted; broken links to missing wiki pages/quizzes; third-party importers passing exceptions via the deprecated two-arg form; locked/hidden files that cannot be copied.","solutions":["Read the opts in the log/migration issues to identify the exact failed item (file, link, or exception)","Fix the source content (restore missing files/links) and re-run the migration","If a custom importer passes an Exception positionally, migrate to opts[:exception] / opts[:error_html] hash form","If warnings are spurious, trace which importer calls add_warning and adjust its pre-checks"],"exampleFix":"// before\nmigration.add_warning(\"file missing\", some_exception) # deprecated positional arg\n// after\nmigration.add_warning(\"file missing\", exception: some_exception)","handlingStrategy":"try-catch","validationCode":"# pre-check referenced content before import\ncourse.attachments.where(id: referenced_ids).missing.each do |a|\n  migration.add_warning(\"missing attachment #{a.name}\")\nend","typeGuard":null,"tryCatchPattern":"begin\n  import_step\nrescue => e\n  migration.add_warning(\"step failed\", exception: e)\nend","preventionTips":["Pass exceptions via opts[:exception], never positionally","Fix source content (missing files/links) before re-running migrations","Review migration warnings in the UI after every course copy","Keep custom importers updated to current add_warning API"],"tags":["migrations","course-copy","content-import","logging"],"backgroundTag":"deprecated-api-usage","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}