puppetlabs/puppet · error · Puppet::Indirector::ValidationError

Invalid instance type %{klass}, expected %{model_type}

Error message

Invalid instance type %{klass}, expected %{model_type}

What it means

Terminus#validate_model uses `model === instance` to check that the object attached to a request is an instance of (or duck-compatible with) the indirection's model class. Saving a foreign object raises Puppet::Indirector::ValidationError naming the actual class and the expected model. This protects termini such as the YAML store from serializing an object of the wrong kind under an indirection (e.g., a Puppet::Node handed to the facts terminus).

Source

Thrown at lib/puppet/indirector/terminus.rb:177

    self.class.terminus_type
  end

  def validate(request)
    if request.instance
      validate_model(request)
      validate_key(request)
    end
  end

  def validate_key(request)
    unless request.key == request.instance.name
      raise Puppet::Indirector::ValidationError, _("Instance name %{name} does not match requested key %{key}") % { name: request.instance.name.inspect, key: request.key.inspect }
    end
  end

  def validate_model(request)
    unless model === request.instance
      raise Puppet::Indirector::ValidationError, _("Invalid instance type %{klass}, expected %{model_type}") % { klass: request.instance.class.inspect, model_type: model.inspect }
    end
  end
end

View on GitHub (pinned to e227c27540)

Solutions

  1. Construct the correct model for the indirection (facts -> Puppet::Node::Facts, node -> Puppet::Node, catalog -> Puppet::Resource::Catalog)
  2. Check the error message: %{klass} tells you what you actually passed; convert it before saving
  3. In tests, build real (or properly subclassed) instances instead of pure doubles when exercising save paths
  4. Use each indirection's own factory methods (Puppet::Node::Facts.new(...)) rather than generic serializers

Example fix

# before
node = Puppet::Node.new('web01')
Puppet::Node::Facts.indirection.save(node, 'web01')
# => Invalid instance type Puppet::Node, expected Puppet::Node::Facts

# after
facts = Puppet::Node::Facts.new('web01', values)
Puppet::Node::Facts.indirection.save(facts, 'web01')
Defensive patterns

Strategy: type-guard

Validate before calling

expected = Puppet::Node::Facts  # the indirection's model
unless expected === instance
  raise Puppet::Indirector::ValidationError, "expected #{expected}, got #{instance.class}"
end

Type guard

def valid_model_instance?(instance, model_class)
  model_class === instance
end

Prevention

When it happens

Trigger: Puppet::Node::Facts.indirection.save(node_instance, key) where node_instance is a Puppet::Node; passing a String or Hash where a model instance is required; stub/mock objects in tests that do not inherit from the model class.

Common situations: Refactorings that change which class is constructed but not which indirection saves it; copy-pasted code between facts and node handling; RSpec doubles substituted for real instances without verifying the model contract.

Related errors


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