{"record":{"id":"a262e59d6ddc39d8","repo":"instructure/canvas-lms","slug":"empty-value","errorCode":null,"errorMessage":"Empty value","messagePattern":"Empty value","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/models/context_external_tool.rb","lineNumber":347,"sourceCode":"          on_by_default: tool.on_by_default?(on_by_default_ids),\n          description: if tool.description\n                         Sanitize.clean(markdown.render(tool.description), CanvasSanitize::SANITIZE)\n                       else\n                         \"\"\n                       end\n        }\n      end\n    end\n\n    def on_by_default_ids\n      Setting.get(\"rce_always_on_developer_key_ids\", \"\").split(\",\").reject(&:empty?).map(&:to_i)\n    end\n\n    private\n\n    def context_id_for(asset, shard)\n      str = asset.asset_string.to_s\n      raise \"Empty value\" if str.blank?\n\n      Canvas::Security.hmac_sha1(str, shard.settings[:encryption_key])\n    end\n\n    def global_navigation_permissions_to_check(root_account)\n      # look at the list of tools that are configured for the account and see if any are asking for permissions checks\n      Rails.cache.fetch_with_batched_keys(\"external_tools/global_navigation/permissions_to_check\", batch_object: root_account, batched_keys: :global_navigation) do\n        tools = all_global_navigation_tools(root_account)\n        tools.filter_map { |tool| tool.extension_setting(:global_navigation, \"required_permissions\")&.split(\",\")&.map(&:to_sym) }.flatten.uniq\n      end\n    end\n\n    def all_global_navigation_tools(root_account)\n      RequestCache.cache(\"global_navigation_tools\", root_account) do # prevent re-querying\n        Lti::ContextToolFinder.new(root_account, type: :global_navigation).all_tools_scope_union.to_unsorted_array\n      end\n    end\n","sourceCodeStart":329,"sourceCodeEnd":365,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/context_external_tool.rb#L329-L365","documentation":"ContextExternalTool.context_id_for HMACs the asset_string of a context asset using the shard's encryption key, and raises \"Empty value\" if the asset's asset_string is blank — a blank asset cannot produce a stable context id.","triggerScenarios":"Calling context_id_for with an asset whose asset_string is empty (e.g. unsaved record with nil id, or an object that does not implement asset_string meaningfully), or a nil/non-asset argument stringified to empty.","commonSituations":"Building tool launch settings for an unsaved context; passing a plain string/hash instead of an AR asset; models missing the asset_string method so to_s yields \"\".","solutions":["Ensure the context record is persisted (has an id) before computing context ids","Pass an actual context asset (Account/Course/etc.) rather than an arbitrary object","Verify the object responds to asset_string with a non-blank value"],"exampleFix":"// before\ncontext_id_for(unsaved_course, shard)\n// after\nraise 'course must be saved' if unsaved_course.new_record?\ncontext_id_for(unsaved_course, shard)","handlingStrategy":"validation","validationCode":"raise 'asset_string required' if asset.nil? || asset.asset_string.to_s.strip.empty?","typeGuard":"->(a) { a.respond_to?(:asset_string) && a.asset_string.present? }","tryCatchPattern":"begin\n  tool.build_context_id(asset, shard)\nrescue RuntimeError => e\n  raise e unless e.message == 'Empty value'\n  Rails.logger.error(\"blank asset_string for #{asset.class}\")\nend","preventionTips":["Persist records before computing context ids","Only pass AR context assets (Course, Account, etc.) into context_id_for","Assert asset_string presence in launch-setting builders"],"tags":["ruby","external-tools","hmac"],"backgroundTag":"empty-required-field","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"}