puppetlabs/puppet · error · ArgumentError

Given argument must be a Hash

Error message

Given argument must be a Hash

What it means

Puppet::InfoService::ClassInformationService#classes_per_environment expects a Hash mapping environment name(s) to a list of manifest files. Any other top-level type (Array, String, nil) raises ArgumentError immediately. This service backs 'puppet parser' / face APIs and PuppetDB/Puppet Server class-info queries, so callers marshalling parameters incorrectly (e.g., a bare file list) hit this guard.

Source

Thrown at lib/puppet/info_service/class_information_service.rb:19

# frozen_string_literal: true

require_relative '../../puppet'
require_relative '../../puppet/pops'
require_relative '../../puppet/pops/evaluator/json_strict_literal_evaluator'

class Puppet::InfoService::ClassInformationService
  def initialize
    @file_to_result = {}
    @parser = Puppet::Pops::Parser::EvaluatingParser.new()
  end

  def classes_per_environment(env_file_hash)
    # In this version of puppet there is only one way to parse manifests, as feature switches per environment
    # are added or removed, this logic needs to change to compute the result per environment with the correct
    # feature flags in effect.

    unless env_file_hash.is_a?(Hash)
      raise ArgumentError, _('Given argument must be a Hash')
    end

    result = {}

    # for each environment
    #   for each file
    #     if file already processed, use last result or error
    #
    env_file_hash.each do |env, files|
      env_result = result[env] = {}
      files.each do |f|
        env_result[f] = result_of(f)
      end
    end
    result
  end

  private

View on GitHub (pinned to e227c27540)

Solutions

  1. Pass a Hash of environment => file list: { 'production' => ['/etc/puppetlabs/code/environments/production/site.pp'] }
  2. If you have a flat file list, wrap it: files.each_with_object({}) { |f,h| (h['production'] ||= []) << f }
  3. Add a type check at the caller boundary and log the received class for debugging
  4. See the face/tool docs for classes_per_environment for the canonical payload shape

Example fix

# before
svc.classes_per_environment(['/etc/puppetlabs/code/site.pp'])
# => ArgumentError: Given argument must be a Hash

# after
svc.classes_per_environment(
  'production' => ['/etc/puppetlabs/code/environments/production/site.pp']
)
Defensive patterns

Strategy: type-guard

Validate before calling

raise ArgumentError, "expected Hash, got #{arg.class}" unless arg.is_a?(Hash)
arg.each_value { |files| raise ArgumentError, 'values must be file lists' unless files.is_a?(Array) }

Type guard

def env_file_hash?(arg)
  arg.is_a?(Hash) && arg.values.all? { |v| v.is_a?(Array) && v.all? { |f| f.is_a?(String) } }
end

Prevention

When it happens

Trigger: Calling Puppet::InfoService::ClassInformationService.new.classes_per_environment(['/path/site.pp']) or (nil) or ('production') instead of { 'production' => ['/path/site.pp'] }; JSON API wrappers that unwrap the payload one level too many and forward an array.

Common situations: Custom tooling or puppetserver routing wrappers that construct the argument from JSON and assume a list; refactorings that change the service signature's shape; exploratory scripts iterating files directly.

Understand the failure class

Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.

Related errors


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