instructure/canvas-lms · error · ArgumentError

Options still contain placeholder: #

Error message

Options still contain placeholder: #{remaining_placeholder[0]}

What it means

After substituting placeholders into deep-duped options, generate_prompt_and_options verifies no String option value still contains a '<\w+_PLACEHOLDER>' token and raises ArgumentError otherwise. It catches option values (e.g. system messages, parameters) referencing placeholders absent from the substitutions hash.

Solutions

  1. Supply the missing key in the substitutions hash matching the placeholder in the option string.
  2. Correct or remove the placeholder in the options hash of the LlmConfig definition.
  3. If a literal '<X_PLACEHOLDER>' string is intended, escape/reword it so it does not match the placeholder pattern.

Example fix

// before
options: { system: "You assist <ROLE_PLACEHOLDER>" }  # called without :ROLE
// after
cfg.generate_prompt_and_options(role: "a tutor", ...)
Defensive patterns

Strategy: validation

Validate before calling

config.options.each_value do |v|
  next unless v.is_a?(String)
  bad = v.scan(/<(\w+)_PLACEHOLDER>/).flatten - substitutions.keys.map(&:to_s)
  raise "unsubstituted option placeholders: #{bad.join(',')}" unless bad.empty?
end

Try / catch

begin
  prompt, opts = config.generate_prompt_and_options(substitutions)
rescue ArgumentError => e
  Rails.logger.error("LLM options placeholder error: #{e.message}")
  raise
end

Prevention

When it happens

Trigger: Calling generate_prompt_and_options where an entry in the config's options hash is a String containing '<FOO_PLACEHOLDER>' and substitutions has no :FOO key.

Common situations: Options templates referencing placeholders only defined for the prompt template; typos in option placeholder names; nested options updated independently from substitutions.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at app/models/llm_config.rb:53

    substitutions.each do |placeholder_prefix, sub_value|
      new_template.gsub!("<#{placeholder_prefix}_PLACEHOLDER>") { sub_value.to_s }
    end

    if (remaining_placeholder = new_template.match(/<\w+_PLACEHOLDER>/))
      raise ArgumentError, "Template still contains placeholder: #{remaining_placeholder[0]}"
    end

    new_options = options.deep_dup

    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)