puppetlabs/puppet · error · Puppet::DataBinding::RecursiveLookupError
Recursive lookup detected in [%{name_stack}]
Error message
Recursive lookup detected in [%{name_stack}] What it means
Invocation#check guards nested named lookups: every provider key lookup (data_provider.rb, module_data_provider.rb) and every interpolation (interpolation.rb) pushes the key onto @name_stack for the duration of resolving it. If the same name appears twice on the stack, resolution is cyclical and Puppet::DataBinding::RecursiveLookupError is raised with the full stack joined by ', '. It converts any nested Puppet::Error (other than LookupError) into a LookupError carrying the cause, so recursion never becomes an infinite loop.
Source
Thrown at lib/puppet/pops/lookup/invocation.rb:93
key = LookupKey.new(key) unless key.is_a?(LookupKey)
@top_key = key
@module_name = module_name.nil? ? key.module_name : module_name
save_current = self.class.current
if save_current.equal?(self)
yield
else
begin
self.class.current = self
yield
ensure
self.class.current = save_current
end
end
end
def check(name)
if @name_stack.include?(name)
raise Puppet::DataBinding::RecursiveLookupError, _("Recursive lookup detected in [%{name_stack}]") % { name_stack: @name_stack.join(', ') }
end
return unless block_given?
@name_stack.push(name)
begin
yield
rescue Puppet::DataBinding::LookupError
raise
rescue Puppet::Error => detail
raise Puppet::DataBinding::LookupError.new(detail.message, nil, nil, nil, detail)
ensure
@name_stack.pop
end
end
def emit_debug_info(preamble)
@explainer.emit_debug_info(preamble) if @explainer.is_a?(DebugExplainer)
endView on GitHub (pinned to e227c27540)
Solutions
- Read the bracketed name stack in the message — it spells out the exact cycle (e.g. [a, b, a]); break the loop at the last link.
- Give the self-referencing value a different key (mykey_base) and interpolate the base key from mykey.
- For structural reuse, use %{alias('other_key')} pointing at a *different* key, never the containing one.
- Re-run `puppet lookup --explain <key>` after the fix to confirm the chain terminates.
Example fix
# before: data/common.yaml
myapp::config: "%{lookup('myapp::config')}/extra"
# after: data/common.yaml
myapp::config_base: "default"
myapp::config: "%{lookup('myapp::config_base')}/extra" Defensive patterns
Strategy: validation
Validate before calling
# Static check: no data value may interpolate its own key
require 'yaml'
Dir['**/data/**/*.yaml'].each do |f|
YAML.load_file(f).to_a.each do |key, value|
next unless value.is_a?(String)
if value.match?(/%\{(?:alias\(|lookup\()?['"]#{Regexp.escape(key.to_s)}['"]/)
abort "#{f}: key '#{key}' interpolates itself"
end
end
end Try / catch
begin
$v = lookup('myapp::config')
rescue Puppet::DataBinding::RecursiveLookupError => e
# e.message shows the cycle, e.g. [a, b, a] — alert data owners, do not loop
notify { "recursive data cycle: #{e.message}": }
$v = undef
end Prevention
- Never interpolate the containing key inside its own value; introduce a *_base key for defaults.
- After editing hiera data, run `puppet lookup --explain <key>` once — cycles surface immediately.
- Review alias() targets to ensure chains of aliases are acyclic across all hierarchy layers.
When it happens
Trigger: Data value that interpolates itself: `mykey: "prefix_%{lookup('mykey')}"`; a %{alias('mykey')} inside mykey's own value; key A interpolates key B which interpolates key A (the name_stack shows the exact cycle); a lookup_options entry whose resolution triggers a lookup of the same key.
Common situations: Refactoring hiera data so a key inherits from itself; typo where the interpolated key equals the containing key; chains of alias() across hierarchy layers that loop; templating logic calling lookup() on the enclosing key.
Related errors
- %{path}: file does not contain a valid yaml hash
- Unable to parse %{message}
- Function lookup() did not find a value for the name '%{name}
- Undefined variable '%{name}'; %{reason}
- Could not find data item %{key} in any Hiera data file and n
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/204c6d2488d07d99.
Report an issue: GitHub.