instructure/canvas-lms · error · ArgumentError
Name must be a string
Error message
Name must be a string
What it means
LlmConfig#validate! runs at construction time and raises ArgumentError when the name passed to the initializer is not a String. The library requires every LLM config to carry a human-readable string name.
Solutions
- Pass the name as a String to LlmConfig.new.
- Coerce at the config-loading boundary (e.g. cfg['name'].to_s or String(cfg['name'])).
- Add schema validation to the YAML/JSON config file so missing names fail early.
Example fix
// before
LlmConfig.new(:gpt4, "gpt-4", nil, template, {})
// after
LlmConfig.new("gpt4", "gpt-4", nil, template, {}) Defensive patterns
Strategy: type-guard
Validate before calling
raise ArgumentError, "name must be String" unless name.is_a?(String) cfg = LlmConfig.new(name, ...)
Type guard
def valid_name?(v) = v.is_a?(String)
Try / catch
begin
cfg = LlmConfig.new(name, ...)
rescue ArgumentError => e
Rails.logger.error("Invalid LlmConfig: #{e.message}")
raise
end Prevention
- Symbolize/normalize config files before constructing LlmConfig
- Coerce with String(name) at load time
- Validate YAML/JSON LLM config schema in CI
When it happens
Trigger: LlmConfig.new(name: nil | symbol | integer | other non-string, ...) — i.e. any constructor call whose first argument is not a String.
Common situations: Loading config from YAML/JSON where name is absent or parsed as another type; passing symbols like :gpt4; dynamically generated configs with nil names.
Related errors
- Model ID must be a string
- Options must be a hash
- Template must be a string
- Rate limit must be either nil, or hash with :limit and…
- A course did not pass validation
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/d20328ca9a8ea943.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/llm_config.rb:63
new_options.each do |key, value|
substitutions.each do |placeholder_prefix, sub_value|
new_options[key] = value.gsub("<#{placeholder_prefix}_PLACEHOLDER>", sub_value.to_s) if value.is_a?(String)
end
end
new_options.each_value do |value|
if value.is_a?(String) && (remaining_placeholder = value.match(/<\w+_PLACEHOLDER>/))
raise ArgumentError, "Options still contain placeholder: #{remaining_placeholder[0]}"
end
end
[new_template, new_options]
end
private
def validate!
raise ArgumentError, "Name must be a string" unless @name.is_a?(String)
raise ArgumentError, "Model ID must be a string" unless @model_id.is_a?(String)
raise ArgumentError, "Rate limit must be either nil, or hash with :limit and :period keys" unless @rate_limit.nil? || (@rate_limit.is_a?(Hash) && @rate_limit.keys == %i[limit period])
raise ArgumentError, "Template must be a string" unless @template.is_a?(String)
raise ArgumentError, "Options must be a hash" unless @options.is_a?(Hash)
end
end
View on GitHub (pinned to 1c9f0bb801)