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
- Supply the missing key in the substitutions hash matching the placeholder in the option string.
- Correct or remove the placeholder in the options hash of the LlmConfig definition.
- 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
- Derive option placeholder names from the same constant set as template placeholders
- Add config-level tests that render every LlmConfig with a full substitutions hash
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
- Template still contains 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/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)