instructure/canvas-lms · error · ArgumentError

Template still contains placeholder: #

Error message

Template still contains placeholder: #{remaining_placeholder[0]}

What it means

generate_prompt_and_options substitutes '<PREFIX_PLACEHOLDER>' tokens in the template string; after substitution it scans for any remaining '<\w+_PLACEHOLDER>' token and raises ArgumentError if one survives. This guards against templates referencing placeholders that were never supplied in the substitutions hash.

Solutions

  1. Add the missing key to the substitutions hash matching the placeholder name exactly (e.g. '<FOO_PLACEHOLDER>' needs key :FOO).
  2. Fix the template text: remove or correct the placeholder spelling.
  3. Log/inspect the template being used in the LlmConfig definition that still contains the stale placeholder.

Example fix

// before
cfg.generate_prompt_and_options(user_name: "Ada")  # template has <USER_ID_PLACEHOLDER>
// after
cfg.generate_prompt_and_options(user_name: "Ada", user_id: 42)
Defensive patterns

Strategy: validation

Validate before calling

missing = template.scan(/<(\w+)_PLACEHOLDER>/).flatten.map(&:downcase).uniq - substitutions.keys.map(&:to_s).map(&:downcase)
raise "unsubstituted placeholders: #{missing.join(',')}" unless missing.empty?

Try / catch

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

Prevention

When it happens

Trigger: Calling LlmConfig#generate_prompt_and_options where the template string contains a '<FOO_PLACEHOLDER>' token for which no matching :FOO key exists in the substitutions hash (or the prefix differs by case/spelling).

Common situations: Typos in template placeholder names; adding a new placeholder to a prompt template without updating callers; renaming a substitutions key but not the template.

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/e10fb6cc931594d1. Report an issue: GitHub.

Appendix: source

Thrown at app/models/llm_config.rb:40

  def initialize(name:, model_id:, rate_limit: nil, template: nil, options: nil)
    @name = name
    @model_id = model_id
    @rate_limit = rate_limit&.transform_keys(&:to_sym)
    @template = template
    @options = options || {}
    validate!
  end

  def generate_prompt_and_options(substitutions:)
    new_template = template.dup

    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

View on GitHub (pinned to 1c9f0bb801)