{"record":{"id":"f821e8e2853dfd6e","repo":"puppetlabs/puppet","slug":"key-is-a-klass-not-a-string-or-symbol","errorCode":null,"errorMessage":"key is a %{klass}, not a string or symbol","messagePattern":"key is a %(.+?), not a string or symbol","errorType":"exception","errorClass":"Puppet::Error","httpStatus":null,"severity":"error","filePath":"lib/puppet/indirector/node/exec.rb","lineNumber":64,"sourceCode":"      if value\n        node.send(param.to_s + \"=\", value)\n      end\n    end\n\n    node.fact_merge(facts)\n    node\n  end\n\n  # Translate the yaml string into Ruby objects.\n  def translate(name, output)\n    Puppet::Util::Yaml.safe_load(output, [Symbol]).each_with_object({}) do |data, hash|\n      case data[0]\n      when String\n        hash[data[0].intern] = data[1]\n      when Symbol\n        hash[data[0]] = data[1]\n      else\n        raise Puppet::Error, _(\"key is a %{klass}, not a string or symbol\") % { klass: data[0].class }\n      end\n    end\n  rescue => detail\n    raise Puppet::Error, _(\"Could not load external node results for %{name}: %{detail}\") % { name: name, detail: detail }, detail.backtrace\n  end\nend\n","sourceCodeStart":46,"sourceCodeEnd":71,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/indirector/node/exec.rb#L46-L71","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before (ENC script, Ruby)\nrequire 'yaml'\nputs YAML.dump({ 123 => 'production', 'classes' => ['apache'] })\n# -> \"123:\\n  - production\\n...\" => key parses as Integer => error\n\n# after\nrequire 'yaml'\nenv = { 123 => 'production', 'classes' => ['apache'] }\nenv.transform_keys(&:to_s)\nputs YAML.dump(env)","handlingStrategy":"validation","validationCode":"output = `#{enc_script} #{node}`\nrequire 'yaml'\ndata = Puppet::Util::Yaml.safe_load(output, [Symbol])\nbad = data.respond_to?(:keys) ? data.keys.reject { |k| k.is_a?(String) || k.is_a?(Symbol) } : []\nraise ArgumentError, \"ENC emitted non-string keys: #{bad.map(&:class).uniq}\" unless bad.empty?","typeGuard":"def valid_enc_keys?(output)\n  data = Puppet::Util::Yaml.safe_load(output, [Symbol])\n  data.is_a?(Enumerable) && data.all? { |pair| pair.is_a?(Array) && (pair[0].is_a?(String) || pair[0].is_a?(Symbol)) }\nend","tryCatchPattern":null,"preventionTips":["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"],"tags":["puppet","external-nodes","enc","yaml","type-mismatch"],"backgroundTag":"yaml-type-mismatch","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}