puppetlabs/puppet · error · Puppet::Error

key is a %{klass}, not a string or symbol

Error message

key is a %{klass}, not a string or symbol

What it means

When the exec node terminus translates the ENC script's YAML output, every top-level key must be a String or Symbol. Puppet::Util::Yaml.safe_load(output, [Symbol]) may legally produce other key classes (YAML 1.1 typing), and any pair whose first element is an Integer, Float, nil, or nested structure raises Puppet::Error identifying the offending class. The classic producer is an ENC emitting unquoted numeric or null keys.

Source

Thrown at lib/puppet/indirector/node/exec.rb:64

      if value
        node.send(param.to_s + "=", value)
      end
    end

    node.fact_merge(facts)
    node
  end

  # Translate the yaml string into Ruby objects.
  def translate(name, output)
    Puppet::Util::Yaml.safe_load(output, [Symbol]).each_with_object({}) do |data, hash|
      case data[0]
      when String
        hash[data[0].intern] = data[1]
      when Symbol
        hash[data[0]] = data[1]
      else
        raise Puppet::Error, _("key is a %{klass}, not a string or symbol") % { klass: data[0].class }
      end
    end
  rescue => detail
    raise Puppet::Error, _("Could not load external node results for %{name}: %{detail}") % { name: name, detail: detail }, detail.backtrace
  end
end

View on GitHub (pinned to e227c27540)

Solutions

  1. Fix the ENC script to stringify all hash keys before dumping: hash.keys.each { |k| hash[k.to_s] = hash.delete(k) }
  2. Quote keys in static ENC YAML output ('123' instead of 123) so they parse as Strings
  3. Run the ENC manually and pipe through ruby -ryaml -e 'puts YAML.safe_load(STDIN, [Symbol]).keys.map(&:class)' to identify the bad key
  4. If keys are intentionally symbolic, emit them as plain symbols (:name) which safe_load accepts via the [Symbol] whitelist

Example fix

# before (ENC script, Ruby)
require 'yaml'
puts YAML.dump({ 123 => 'production', 'classes' => ['apache'] })
# -> "123:\n  - production\n..." => key parses as Integer => error

# after
require 'yaml'
env = { 123 => 'production', 'classes' => ['apache'] }
env.transform_keys(&:to_s)
puts YAML.dump(env)
Defensive patterns

Strategy: validation

Validate before calling

output = `#{enc_script} #{node}`
require 'yaml'
data = Puppet::Util::Yaml.safe_load(output, [Symbol])
bad = data.respond_to?(:keys) ? data.keys.reject { |k| k.is_a?(String) || k.is_a?(Symbol) } : []
raise ArgumentError, "ENC emitted non-string keys: #{bad.map(&:class).uniq}" unless bad.empty?

Type guard

def valid_enc_keys?(output)
  data = Puppet::Util::Yaml.safe_load(output, [Symbol])
  data.is_a?(Enumerable) && data.all? { |pair| pair.is_a?(Array) && (pair[0].is_a?(String) || pair[0].is_a?(Symbol)) }
end

Prevention

When it happens

Trigger: An ENC script outputs YAML like "123: production" or "~: value" or "1.5: x"; safe_load yields Integer/nil/Float keys, the case statement in translate() falls through to else, and the error names data[0].class. Also hit when the ENC emits a hash whose keys are arrays/hashes.

Common situations: ENC scripts that build hashes from numeric IDs (environment IDs, role numbers) and serialize with YAML.dump without stringifying keys; migrations from YAML 1.1 tools where unquoted values are auto-typed; hand-written ENC test fixtures with sloppy quoting.

Related errors


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