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
- Fix the ENC script to stringify all hash keys before dumping: hash.keys.each { |k| hash[k.to_s] = hash.delete(k) }
- Quote keys in static ENC YAML output ('123' instead of 123) so they parse as Strings
- Run the ENC manually and pipe through ruby -ryaml -e 'puts YAML.safe_load(STDIN, [Symbol]).keys.map(&:class)' to identify the bad key
- 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
- Always stringify keys before YAML.dump in ENC scripts (transform_keys(&:to_s))
- Include an ENC output linter in the ENC script's CI (parse + assert key types)
- Prefer quoted keys in static YAML templates
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
- Could not load external node results for %{name}: %{detail}
- You must set the 'external_nodes' parameter to use the exter
- Serialized YAML did not contain a collection of instances wh
- Serialized YAML did not contain a valid instance of %{klass}
- --values_file option must evaluate to a Hash or undef/nil, g
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/f821e8e2853dfd6e.
Report an issue: GitHub.