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

  1. Pass model_id as a String.
  2. Convert numeric or symbolic ids with .to_s before constructing the config.
  3. 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

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


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)