instructure/canvas-lms · error · ArgumentError
invalid scope for hash
Error message
invalid scope for hash
What it means
CustomData#hash_data_from_scope walks a '/'-delimited scope through a nested hash and raises ArgumentError as soon as an intermediate value is not a Hash. get_data uses it, so reading a scope whose path points into (or through) a non-hash value fails.
Solutions
- Correct the scope string so every intermediate segment corresponds to a nested hash.
- Delete the conflicting leaf data and re-store it with the correct nested shape via set_data.
- Before reading, call get_data on the parent scope and verify it is a Hash.
Example fix
// before
data = custom_data.get_data(user, 'profile', 'settings/theme/colors')
// after
settings = custom_data.get_data(user, 'profile', 'settings')
raise ArgumentError, 'settings is not a hash' unless settings.is_a?(Hash)
data = settings.dig('theme', 'colors') Defensive patterns
Strategy: validation
Validate before calling
parent = custom_data.get_data(user, namespace, 'settings') raise ArgumentError, 'settings is not a hash' unless parent.is_a?(Hash)
Type guard
def hash_at_path?(data, scope) = scope.split('/').inject(data) { |h, k| h.is_a?(Hash) ? h[k] : nil }.is_a?(Hash) Try / catch
begin
data = custom_data.get_data(user, namespace, scope)
rescue ArgumentError => e
Rails.logger.warn("bad custom_data scope #{scope}: #{e.message}")
data = nil
end Prevention
- Keep scope schemas stable; never overwrite a hash node with a scalar
- Validate scope strings against a documented schema
- Read the parent scope and confirm it is a Hash before deep reads
When it happens
Trigger: Calling get_data(user, namespace, scope) where the scope path traverses a key that was previously set to a scalar (e.g. set_data with scope 'a/b' then get_data with scope 'a/b/c').
Common situations: Scope schema drift: a value once stored as a leaf string is now treated as a nested namespace; typos in scope strings; different app versions writing conflicting shapes under one namespace.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- message_settings must be an Array if present
- all elements must be User objects
- block is required if module_name is not passed
- Cannot include a concatenation in a merge.
- cannot pass both a module_name and a block
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/bf59f387f756f843.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/custom_data.rb:72
yield
destroyed? || save
end
end
def set_data(scope, val)
set_hash_data_from_scope(data_json, "d/#{scope}", val)
end
def delete_data(scope)
delete_hash_data_from_scope(data_json, "d/#{scope}")
end
private
def hash_data_from_scope(hash, scope)
keys = scope.split("/")
keys.inject(hash) do |h, k|
raise ArgumentError, "invalid scope for hash" unless h.is_a?(Hash)
h[k]
end
end
def set_hash_data_from_scope(hash, scope, data)
keys = scope.split("/")
last = keys.pop
traverse = lambda do |hsh, key_idx|
return hsh if key_idx == keys.length
k = keys[key_idx]
h = hsh[k]
if h.nil?
hsh[k] = {}
elsif !h.is_a? Hash
raise WriteConflict.new({View on GitHub (pinned to 1c9f0bb801)