puppetlabs/puppet · error · Puppet::Error
Attempt to assign variable %{name} when evaluating parameter
Error message
Attempt to assign variable %{name} when evaluating parameters What it means
Raised by ParameterScope#[]= while a parameter default expression is being evaluated. evaluate()/evaluate3x() wrap default evaluation in as_read_only, which sets @read_only = true; any write into a parameter during that window (variable assignment landing on the ParameterScope) is rejected. Its purpose is to keep default expressions side-effect free, since assigning to parameters mid-evaluation would corrupt the ordered evaluation contract.
Source
Thrown at lib/puppet/parser/scope.rb:234
private :parameter_reference_failure
def initialize(parent, callee_name, param_names)
super(parent)
@callee_name = callee_name
@params = {}
param_names.each { |name| @params[name] = Access.new }
end
def [](name)
access = @params[name]
return super if access.nil?
throw(:unevaluated_parameter, name) unless access.assigned?
access.value
end
def []=(name, value)
raise Puppet::Error, _("Attempt to assign variable %{name} when evaluating parameters") % { name: name } if @read_only
@params[name] ||= Access.new
@params[name].value = value
end
def bound?(name)
@params.include?(name)
end
def include?(name)
@params.include?(name) || super
end
def is_local_scope?
true
end
def as_read_onlyView on GitHub (pinned to e227c27540)
Solutions
- Make the helper side-effect free: have the function RETURN a value used as the default instead of assigning a scope variable: `$a = my_func()`.
- Move the assignment out of the default expression into the class body: `class c($a = undef) { $a = my_func() if $a == undef }`.
- If you maintain the Ruby function, replace scope.setvar with a return value, or write to a named (non-parameter) scope explicitly.
Example fix
# before (3.x function writes scope)
module Puppet::Parser::Functions
newfunction(:set_port) do |args|
self.scope.setvar('port', args[0]) # raises when called from a default
end
end
class app($port = set_port(8080)) { }
# after
module Puppet::Parser::Functions
newfunction(:get_port) do |args|
args[0]
end
end
class app($port = get_port(8080)) { } Defensive patterns
Strategy: validation
Validate before calling
# Audit Ruby 3.x functions for scope mutation before reusing them in defaults: # rg "setvar|scope\[.*\]\s*=" lib/ # A default expression may only CALL functions that return values.
Prevention
- Keep parameter default expressions pure: no function that writes scope variables.
- When writing 3.x functions, return values instead of scope.setvar; reserve setvar for legacy call sites.
- Smoke-test classes with defaults in CI compilation so read-only violations surface before production.
When it happens
Trigger: A parameter default whose evaluation assigns a scope variable that resolves into the ParameterScope — e.g. a legacy 3.x Ruby function called from a default expression that does scope.setvar / scope['x'] = ..., or an expression whose block/lambda shares the guarded scope and performs `$x = ...`. Example: `class c($a = set_scope_var('x'))` where the 3.x function writes scope['x']. API users hit it by calling ParameterScope#[]= directly while @read_only is true.
Common situations: Old 3.x-style modules whose Ruby functions mutate the scope being reused under Puppet 4+ parameter evaluation; wrapping default expressions with functions that cache results in scope variables; upgrading Puppet versions where evaluation order/read-only enforcement became stricter.
Related errors
- Undefined variable '%{name}'; %{reason}
- %{callee}: expects a value for parameter $%{to}
- %{callee}: default expression for $%{from} tries to illegall
- Undefined variable '%{name}'; %{reason}
- Cannot assign to a numeric match result variable '$%{name}'
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/153ce0bc8d720d65.
Report an issue: GitHub.