puppetlabs/puppet · error · Puppet::ParseError

attribute %{label}[%{key}] is defined as both a constant and

Error message

attribute %{label}[%{key}] is defined as both a constant and an attribute

What it means

In an Object type definition, entries under constants are folded into the attribute map as constant-kind attributes. Before doing so, Puppet checks that no key appears in both constants and attributes; a Puppet::ParseError is raised because the two declarations would conflict over the same member name.

Source

Thrown at lib/puppet/pops/types/p_object_type.rb:742

        end
        param_type = POptionalType.new(param_type) unless param_type.is_a?(POptionalType)
        type_param = PTypeParameter.new(key, self, KEY_TYPE => param_type, KEY_VALUE => param_value).assert_override(parent_type_params)
        @type_parameters[key] = type_param
      end
    end

    constants = init_hash[KEY_CONSTANTS]
    attr_specs = init_hash[KEY_ATTRIBUTES]
    if attr_specs.nil?
      attr_specs = {}
    else
      # attr_specs might be frozen
      attr_specs = attr_specs.to_h
    end
    unless constants.nil? || constants.empty?
      constants.each do |key, value|
        if attr_specs.include?(key)
          raise Puppet::ParseError, _("attribute %{label}[%{key}] is defined as both a constant and an attribute") % { label: label, key: key }
        end

        attr_spec = {
          # Type must be generic here, or overrides would become impossible
          KEY_TYPE => TypeCalculator.infer(value).generalize,
          KEY_VALUE => value,
          KEY_KIND => ATTRIBUTE_KIND_CONSTANT
        }
        # Indicate override if parent member exists. Type check etc. will take place later on.
        attr_spec[KEY_OVERRIDE] = parent_members.include?(key)
        attr_specs[key] = attr_spec
      end
    end

    unless attr_specs.empty?
      @attributes = attr_specs.to_h do |key, attr_spec|
        unless attr_spec.is_a?(Hash)
          attr_type = TypeAsserter.assert_instance_of(nil, PTypeType::DEFAULT, attr_spec) { "attribute #{label}[#{key}]" }

View on GitHub (pinned to e227c27540)

Solutions

  1. Pick one form: if the value should be settable per instance, keep it in attributes and delete it from constants; if fixed forever, keep only constants
  2. Run puppet parser validate or a catalog compile to catch it before deployment

Example fix

# before
type MyApp::Cfg = Object[{
  attributes => { mode => String },
  constants => { mode => 'read' }
}]

# after
type MyApp::Cfg = Object[{
  attributes => { mode => String[1] }
}]
Defensive patterns

Strategy: validation

Validate before calling

# before building the init hash / shipping a type definition
overlap = (constants.keys & attributes.keys)
raise "keys declared both constant and attribute: #{overlap.inspect}" unless overlap.empty?

Try / catch

begin
  Puppet::Pops::Types::PObjectType.new(name, init_hash)
rescue Puppet::ParseError => e
  raise unless e.message =~ /defined as both a constant and an attribute/
  raise  # e.message names the offending key; remove it from one block
end

Prevention

When it happens

Trigger: A type definition hash containing the same key in both blocks, e.g. attributes => { mode => String } and constants => { mode => 'read' }. Reachable from Puppet DSL type declarations, Ruby PObjectType init hashes, and pcore-loaded type definitions.

Common situations: Promoting a constant to a real attribute (or vice versa) and forgetting to delete the old block; merging type definitions programmatically; copy-paste between type definitions.

Related errors


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