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

: file does not contain a valid yaml hash

Error message

%{path}: file does not contain a valid yaml hash

What it means

Puppet's built-in Hiera 'yaml_data' data_hash function loads each YAML file with Puppet::Util::Yaml.safe_load and expects a top-level mapping. If the document is anything other than a Hash (a sequence, a scalar, or a comments-only file that parses as nil), the data cannot serve key lookups: under strict=error Puppet raises Puppet::DataBinding::LookupError, otherwise it logs a warning and treats the file as empty. A literal 'false' document is exempted from the raise because empty YAML files can historically parse as false.

Solutions

  1. Reformat the file named in the message so the top level is a mapping (keys at column 0, list items indented under a key).
  2. Run 'puppet lookup <key> --explain' to see exactly which path failed.
  3. Sanity-check with: ruby -ryaml -e "p YAML.safe_load(File.read('file.yaml')).is_a?(Hash)".
  4. Tighten path/glob patterns so non-data YAML files are never matched.

Example fix

# datadir/common.yaml - before (top-level list)
- ntp::servers: ['pool.ntp.org']
- profile::timezone: 'UTC'

# after (top-level mapping)
ntp::servers:
  - 'pool.ntp.org'
profile::timezone: 'UTC'
Defensive patterns

Strategy: validation

Validate before calling

require 'yaml'

def valid_hiera_data?(path)
  doc = YAML.safe_load(File.read(path), permitted_classes: [], aliases: false)
  doc.is_a?(Hash)
rescue Psych::SyntaxError, StandardError
  false
end

bad = Dir['data/**/*.yaml'].reject { |f| valid_hiera_data?(f) }
abort "top-level node is not a mapping: #{bad.join(', ')}" unless bad.empty?

Try / catch

begin
  Puppet::LookupContext.new.lookup('mykey')
rescue Puppet::DataBinding::LookupError => e
  # message contains the offending path; fall back to a default value
  Puppet.err "hiera data invalid: #{e.message}"
  default_value
end

Prevention

When it happens

Trigger: hiera.yaml declares data_hash: yaml_data with path/paths/glob/mapped_paths selecting a file whose top-level node is a YAML list ('- key: value' at column 0), a bare scalar, or an empty/comments-only file; or a broad glob (e.g. 'data/*.yaml') matches a non-Hiera YAML file. The raise additionally requires Puppet[:strict] == :error and the parsed value != false.

Common situations: Writing hiera data as a top-level list instead of nesting it under a key; a truncated or half-deployed file; a glob picking up unrelated YAML (k8s manifests, docker-compose); an empty file created by a failed template render; CI passing locally but failing on a strict-mode master.

Related errors


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

Appendix: source

Thrown at lib/puppet/functions/yaml_data.rb:31

  dispatch :yaml_data do
    param 'Struct[{path=>String[1]}]', :options
    param 'Puppet::LookupContext', :context
  end

  argument_mismatch :missing_path do
    param 'Hash', :options
    param 'Puppet::LookupContext', :context
  end

  def yaml_data(options, context)
    path = options['path']
    context.cached_file_data(path) do |content|
      data = Puppet::Util::Yaml.safe_load(content, [Symbol], path)
      if data.is_a?(Hash)
        Puppet::Pops::Lookup::HieraConfig.symkeys_to_string(data)
      else
        msg = _("%{path}: file does not contain a valid yaml hash" % { path: path })
        raise Puppet::DataBinding::LookupError, msg if Puppet[:strict] == :error && data != false

        Puppet.warning(msg)
        {}
      end
    rescue Puppet::Util::Yaml::YamlLoadError => ex
      # YamlLoadErrors include the absolute path to the file, so no need to add that
      raise Puppet::DataBinding::LookupError, _("Unable to parse %{message}") % { message: ex.message }
    end
  end

  def missing_path(options, context)
    "one of 'path', 'paths' 'glob', 'globs' or 'mapped_paths' must be declared in hiera.yaml when using this data_hash function"
  end
end

View on GitHub (pinned to e227c27540)