instructure/canvas-lms · error · ArgumentError
Model ID must be a string
Error message
Model ID must be a string
What it means
Guard in RruleHelper#parse_bymonthday: a yearly RRULE's BYMONTHDAY contains more than one day (comma-separated list), which Canvas's recurring calendar events do not support. Raises RruleValidationError — an input-validation sentinel against the user-supplied RRULE string.
Solutions
- Pass model_id as a String.
- Convert numeric or symbolic ids with .to_s before constructing the config.
- Validate the model registry/config file so every entry has a string model id.
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, "model_id must be String" unless model_id.is_a?(String) cfg = LlmConfig.new(name, model_id, ...)
Type guard
def valid_model_id?(v) = v.is_a?(String)
Try / catch
begin
cfg = LlmConfig.new(name, model_id, ...)
rescue ArgumentError => e
Rails.logger.error("Invalid LlmConfig: #{e.message}")
raise
end Prevention
- Call .to_s on ids coming from symbols or numbers
- Assert model ids in the config registry are strings in a startup check
When it happens
Trigger: LlmConfig.new(name, model_id where model_id is nil, symbol, hash, etc.) — any non-String model identifier.
Common situations: YAML keys parsed as symbols; nil model ids from incomplete config files; passing a model object instead of its id string.
Related errors
- Name 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/6e46f7bd195aabaf.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/llm_config.rb:64
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)