instructure/canvas-lms · error

Empty value

Error message

Empty value

What it means

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.

Solutions

  1. Ensure the context record is persisted (has an id) before computing context ids
  2. Pass an actual context asset (Account/Course/etc.) rather than an arbitrary object
  3. Verify the object responds to asset_string with a non-blank value

Example fix

// before
context_id_for(unsaved_course, shard)
// after
raise 'course must be saved' if unsaved_course.new_record?
context_id_for(unsaved_course, shard)
Defensive patterns

Strategy: validation

Validate before calling

raise 'asset_string required' if asset.nil? || asset.asset_string.to_s.strip.empty?

Type guard

->(a) { a.respond_to?(:asset_string) && a.asset_string.present? }

Try / catch

begin
  tool.build_context_id(asset, shard)
rescue RuntimeError => e
  raise e unless e.message == 'Empty value'
  Rails.logger.error("blank asset_string for #{asset.class}")
end

Prevention

When it happens

Trigger: 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.

Common situations: 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 "".

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at app/models/context_external_tool.rb:347

          on_by_default: tool.on_by_default?(on_by_default_ids),
          description: if tool.description
                         Sanitize.clean(markdown.render(tool.description), CanvasSanitize::SANITIZE)
                       else
                         ""
                       end
        }
      end
    end

    def on_by_default_ids
      Setting.get("rce_always_on_developer_key_ids", "").split(",").reject(&:empty?).map(&:to_i)
    end

    private

    def context_id_for(asset, shard)
      str = asset.asset_string.to_s
      raise "Empty value" if str.blank?

      Canvas::Security.hmac_sha1(str, shard.settings[:encryption_key])
    end

    def global_navigation_permissions_to_check(root_account)
      # look at the list of tools that are configured for the account and see if any are asking for permissions checks
      Rails.cache.fetch_with_batched_keys("external_tools/global_navigation/permissions_to_check", batch_object: root_account, batched_keys: :global_navigation) do
        tools = all_global_navigation_tools(root_account)
        tools.filter_map { |tool| tool.extension_setting(:global_navigation, "required_permissions")&.split(",")&.map(&:to_sym) }.flatten.uniq
      end
    end

    def all_global_navigation_tools(root_account)
      RequestCache.cache("global_navigation_tools", root_account) do # prevent re-querying
        Lti::ContextToolFinder.new(root_account, type: :global_navigation).all_tools_scope_union.to_unsorted_array
      end
    end

View on GitHub (pinned to 1c9f0bb801)