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
- Add the missing key to the substitutions hash matching the placeholder name exactly (e.g. '<FOO_PLACEHOLDER>' needs key :FOO).
- Fix the template text: remove or correct the placeholder spelling.
- 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
- Keep placeholder names and substitution keys in one shared constant list
- Add a spec asserting each production template has zero remaining placeholders with default substitutions
- Grep templates for _PLACEHOLDER after renaming substitution keys
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
- Options still contain placeholder: #
- AI response appears truncated - the response may have…
- Cannot find criterion with id #
- Cannot pass url and use block
- Cannot regenerate criteria with learning outcomes attached
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]
endView on GitHub (pinned to 1c9f0bb801)