{"record":{"id":"204c6d2488d07d99","repo":"puppetlabs/puppet","slug":"recursive-lookup-detected-in-name-stack","errorCode":null,"errorMessage":"Recursive lookup detected in [%{name_stack}]","messagePattern":"Recursive lookup detected in \\[%(.+?)\\]","errorType":"exception","errorClass":"Puppet::DataBinding::RecursiveLookupError","httpStatus":null,"severity":"error","filePath":"lib/puppet/pops/lookup/invocation.rb","lineNumber":93,"sourceCode":"    key = LookupKey.new(key) unless key.is_a?(LookupKey)\n    @top_key = key\n    @module_name = module_name.nil? ? key.module_name : module_name\n    save_current = self.class.current\n    if save_current.equal?(self)\n      yield\n    else\n      begin\n        self.class.current = self\n        yield\n      ensure\n        self.class.current = save_current\n      end\n    end\n  end\n\n  def check(name)\n    if @name_stack.include?(name)\n      raise Puppet::DataBinding::RecursiveLookupError, _(\"Recursive lookup detected in [%{name_stack}]\") % { name_stack: @name_stack.join(', ') }\n    end\n    return unless block_given?\n\n    @name_stack.push(name)\n    begin\n      yield\n    rescue Puppet::DataBinding::LookupError\n      raise\n    rescue Puppet::Error => detail\n      raise Puppet::DataBinding::LookupError.new(detail.message, nil, nil, nil, detail)\n    ensure\n      @name_stack.pop\n    end\n  end\n\n  def emit_debug_info(preamble)\n    @explainer.emit_debug_info(preamble) if @explainer.is_a?(DebugExplainer)\n  end","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/pops/lookup/invocation.rb#L75-L111","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before: data/common.yaml\nmyapp::config: \"%{lookup('myapp::config')}/extra\"\n\n# after: data/common.yaml\nmyapp::config_base: \"default\"\nmyapp::config: \"%{lookup('myapp::config_base')}/extra\"","handlingStrategy":"validation","validationCode":"# Static check: no data value may interpolate its own key\nrequire 'yaml'\n\nDir['**/data/**/*.yaml'].each do |f|\n  YAML.load_file(f).to_a.each do |key, value|\n    next unless value.is_a?(String)\n    if value.match?(/%\\{(?:alias\\(|lookup\\()?['\"]#{Regexp.escape(key.to_s)}['\"]/)\n      abort \"#{f}: key '#{key}' interpolates itself\"\n    end\n  end\nend","typeGuard":null,"tryCatchPattern":"begin\n  $v = lookup('myapp::config')\nrescue Puppet::DataBinding::RecursiveLookupError => e\n  # e.message shows the cycle, e.g. [a, b, a] — alert data owners, do not loop\n  notify { \"recursive data cycle: #{e.message}\": }\n  $v = undef\nend","preventionTips":["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."],"tags":["puppet","hiera","interpolation","recursion","data-binding"],"backgroundTag":"hiera-recursive-lookup","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}