puppetlabs/puppet · error · ArgumentError

The code loaded from %{source_ref} contains additional logic

Error message

The code loaded from %{source_ref} contains additional logic - can only contain the type '%{name}'

What it means

After all other checks pass, TypeDefinitionInstantiator.create verifies model.body == type_definition: the type definition must be the entire program body. If the file has extra top-level logic around it — statements, expression, or anything that is not the single definition — this ArgumentError fires. It is the 'your type file contains code besides the type' guard.

Source

Thrown at lib/puppet/pops/loader/type_definition_instantiator.rb:38

    else
      raise ArgumentError,
            _("The code loaded from %{source_ref} must contain only the type '%{name}' - it has additional definitions.") % { source_ref: source_ref, name: name }
    end
    type_definition = model.definitions[0]

    unless type_definition.is_a?(Model::TypeAlias) || type_definition.is_a?(Model::TypeDefinition)
      raise ArgumentError,
            _("The code loaded from %{source_ref} does not define the type '%{name}' - no type alias or type definition found.") % { source_ref: source_ref, name: name }
    end

    actual_name = type_definition.name
    unless name == actual_name.downcase
      raise ArgumentError,
            _("The code loaded from %{source_ref} produced type with the wrong name, expected '%{name}', actual '%{actual_name}'") % { source_ref: source_ref, name: name, actual_name: actual_name }
    end

    unless model.body == type_definition
      raise ArgumentError,
            _("The code loaded from %{source_ref} contains additional logic - can only contain the type '%{name}'") % { source_ref: source_ref, name: name }
    end

    # Adapt the type definition with loader - this is used from logic contained in its body to find the
    # loader to use when resolving contained aliases API. Such logic have a hard time finding the closure (where
    # the loader is known - hence this mechanism
    private_loader = loader.private_loader
    Adapters::LoaderAdapter.adapt(type_definition).loader_name = private_loader.loader_name
    create_runtime_type(type_definition)
  end

  def self.create_from_model(type_definition, loader)
    typed_name = TypedName.new(:type, type_definition.name)
    type = create_runtime_type(type_definition)
    loader.set_entry(
      typed_name,
      type,
      type_definition.locator.to_uri(type_definition)

View on GitHub (pinned to e227c27540)

Solutions

  1. Strip the file down to the single `type ...` statement; remove notices, assignments, conditionals.
  2. Move any supporting logic into a function or class manifest, not the types/ file.
  3. Note the earlier checks still apply — keep it exactly one definition and nothing else.

Example fix

// before: modules/mymodule/types/mytype.pp
notice('loading mytype')
type mymodule::mytype = String

// after: modules/mymodule/types/mytype.pp
type mymodule::mytype = String
Defensive patterns

Strategy: validation

Validate before calling

# CI check: types/*.pp program body must be the single definition (no extra logic)
require 'puppet'
require 'puppet/pops'

Dir['modules/*/types/**/*.pp'].each do |f|
  model = Puppet::Pops::Parser::EvaluatingParser.new.parse_string(File.read(f), f)
  abort "#{f}: contains additional logic beyond the type definition" unless model.body == model.definitions[0]
end

Prevention

When it happens

Trigger: types/mytype.pp like `notice('hi')` before/after the alias, a variable assignment `$x = 1` at top level, or an `if` statement wrapping the type definition; the definitions count is 1 so earlier checks pass, but the program body is a Model::Program whose body is not the definition itself.

Common situations: Debug statements left in a type file; conditional type definition attempts; people treating types/ files like normal manifests.

Related errors


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