puppetlabs/puppet · error · Puppet::DataBinding::LookupError

value of %{opts} must be a hash

Error message

value of %{opts} must be a hash

What it means

LookupAdapter#validate_lookup_options is the entry gate for the special 'lookup_options' key found in data: before any pattern or key handling, it requires the value to be a Hash, otherwise Puppet::DataBinding::LookupError 'value of lookup_options must be a hash' is raised. lookup_options must always be a mapping of data keys (or '^' regex patterns) to option hashes like {'convert_to' => ..., 'merge' => ...}.

Source

Thrown at lib/puppet/pops/lookup/lookup_adapter.rb:300

  def global_only?
    instance_variable_defined?(:@global_only) ? @global_only : false
  end

  # Instructs the lookup framework to only perform lookups in the global layer
  # @return [LookupAdapter] self
  def set_global_only
    @global_only = true
    self
  end

  private

  PROVIDER_STACK = [:lookup_global, :lookup_in_environment, :lookup_in_module].freeze

  def validate_lookup_options(options, module_name)
    return nil if options.nil?
    raise Puppet::DataBinding::LookupError, _("value of %{opts} must be a hash") % { opts: LOOKUP_OPTIONS } unless options.is_a?(Hash)
    return options if module_name.nil?

    pfx = "#{module_name}::"
    options.each_pair do |key, _value|
      if key.start_with?(LOOKUP_OPTIONS_PATTERN_START)
        unless key[1..pfx.length] == pfx
          raise Puppet::DataBinding::LookupError, _("all %{opts} patterns must match a key starting with module name '%{module_name}'") % { opts: LOOKUP_OPTIONS, module_name: module_name }
        end
      else
        unless key.start_with?(pfx)
          raise Puppet::DataBinding::LookupError, _("all %{opts} keys must start with module name '%{module_name}'") % { opts: LOOKUP_OPTIONS, module_name: module_name }
        end
      end
    end
  end

  def compile_patterns(options)
    return nil if options.nil?

View on GitHub (pinned to e227c27540)

Solutions

  1. Make lookup_options a mapping: `lookup_options: { 'mykey': { 'merge' => 'deep' } }`.
  2. If you do not want options, delete the lookup_options entry entirely rather than setting a non-hash value.
  3. Run the data file through a YAML linter — indentation mistakes are the usual root cause.

Example fix

# before: data/common.yaml
lookup_options: 'enabled'

# after
lookup_options:
  myapp::config:
    merge: deep
Defensive patterns

Strategy: validation

Validate before calling

# CI check: any lookup_options key in data must map to a Hash
require 'yaml'

Dir['**/data/**/*.yaml'].each do |f|
  lo = YAML.load_file(f)['lookup_options']
  next if lo.nil?
  abort "#{f}: lookup_options must be a Hash, got #{lo.class}" unless lo.is_a?(Hash)
end

Prevention

When it happens

Trigger: A data file containing `lookup_options: true`, `lookup_options: [a, b]`, or `lookup_options: 'auto'` (string); YAML indentation error making the value a scalar; using lookup_options as a flag instead of a map.

Common situations: Teams toggling lookup_options on/off as a boolean; malformed YAML where nested options de-indented into a scalar; copy-paste from notes that drop the mapping structure.

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/05ceb0600b8fddce. Report an issue: GitHub.