{"record":{"id":"fb67977c0c7d87f6","repo":"instructure/canvas-lms","slug":"must-call-load-tags-first","errorCode":null,"errorMessage":"must call `load_tags!` first","messagePattern":"must call `load_tags!` first","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"app/models/master_courses/tag_helper.rb","lineNumber":82,"sourceCode":"      tag\n    else\n      content_tags.where(content:).first || create_content_tag_for!(content, defaults)\n    end\n  end\n\n  def create_content_tag_for!(content, defaults = {})\n    return if content.is_a?(Assignment) && Assignment::SUBMITTABLE_TYPES.include?(content.submission_types)\n\n    self.class.unique_constraint_retry do |retry_count|\n      tag = nil\n      tag = content_tags.where(content:).first if retry_count > 0\n      tag ||= content_tags.create!(defaults.merge(content:))\n      tag\n    end\n  end\n\n  def cached_content_tag_for(content)\n    raise \"must call `load_tags!` first\" unless @content_tag_index\n\n    if content.is_a?(Assignment) && (submittable = content.submittable_object)\n      content = submittable # use one child tag\n    end\n    @content_tag_index.dig(content.class.base_class.name, content.id)\n  end\nend\n","sourceCodeStart":64,"sourceCodeEnd":90,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/master_courses/tag_helper.rb#L64-L90","documentation":"MasterCourses::TagHelper caches content tags in an in-memory index built by load_tags!. cached_content_tag_for looks up that index; if load_tags! was never called, @content_tag_index is nil and it raises 'must call `load_tags!` first'.","triggerScenarios":"Calling helper.cached_content_tag_for(content) on a fresh MasterCourses::TagHelper instance without first calling load_tags! for the relevant migration/master course.","commonSituations":"Custom migration plugins using the helper directly; tests instantiating TagHelper manually; calling the lookup before the helper is initialized for the current migration step.","solutions":["Call load_tags! on the helper before any cached_content_tag_for lookup","Reuse the migration's existing tag_helper instance (e.g. via the migration object) instead of creating a new one","In tests, call load_tags! in a before-hook"],"exampleFix":"// before\nhelper = MasterCourses::TagHelper.new(migration)\ntag = helper.cached_content_tag_for(assignment)\n// after\nhelper = MasterCourses::TagHelper.new(migration)\nhelper.load_tags!\ntag = helper.cached_content_tag_for(assignment)","handlingStrategy":"validation","validationCode":"helper.load_tags! if helper.instance_variable_get(:@content_tag_index).nil?","typeGuard":"def tags_loaded?(helper)\n  !helper.instance_variable_get(:@content_tag_index).nil?\nend","tryCatchPattern":"begin\n  tag = helper.cached_content_tag_for(content)\nrescue RuntimeError => e\n  raise unless e.message.include?('load_tags!')\n  helper.load_tags!\n  tag = helper.cached_content_tag_for(content)\nend","preventionTips":["Encapsulate TagHelper creation in a factory that always calls load_tags!","Add test hooks asserting the index is loaded before lookups"],"tags":["ruby","blueprint-courses","invalid-state"],"backgroundTag":"invalid-state-transition","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"}