puppetlabs/puppet · error · ArgumentError

attempt to #{action} #{type_name} annotation declared on #{o

Error message

attempt to #{action} #{type_name} annotation declared on #{o.label}

What it means

Annotation.annotate_new(o, init_hash) forces creation or clearing of a Pcore annotation. When o is Annotatable (e.g. a Pcore type declaration or typeset member) and its annotations hash already contains this annotation type, the annotation is considered 'declared on the type' and is immutable at runtime: passing the CLEAR sentinel (the string 'clear') raises 'attempt to clear ...', any other init_hash raises 'attempt to redefine ...'. The message interpolates the annotation type name and o.label (the type/object label).

Source

Thrown at lib/puppet/pops/types/annotation.rb:55

        end
        adapter = associate_adapter(_pcore_type.from_hash(init_hash), o) unless init_hash.nil?
      end
      adapter
    end

    # Forces the creation or removal of an annotation of this type.
    # If `init_hash` is a hash, a new annotation is created and returned
    # If `init_hash` is `nil`, then the annotation is cleared and the previous annotation is returned.
    #
    # @param o [Object] object to annotate
    # @param init_hash [Hash{String,Object},nil] the initializer for the annotation or `nil` to clear the annotation
    # @return [Annotation<self>] an annotation of the same class as the receiver of the call
    #
    def self.annotate_new(o, init_hash)
      if o.is_a?(Annotatable) && o.annotations.include?(_pcore_type)
        # Prevent clear or redefine of annotations declared on type
        action = init_hash == CLEAR ? 'clear' : 'redefine'
        raise ArgumentError, "attempt to #{action} #{type_name} annotation declared on #{o.label}"
      end

      if init_hash == CLEAR
        clear(o)
      else
        associate_adapter(_pcore_type.from_hash(init_hash), o)
      end
    end

    # Uses name of type instead of name of the class (the class is likely dynamically generated and as such,
    # has no name)
    # @return [String] the name of the type
    def self.type_name
      _pcore_type.name
    end
  end
end
end

View on GitHub (pinned to e227c27540)

Solutions

  1. Do not clear or redefine annotations that come from the type declaration - treat them as read-only metadata
  2. For per-instance mutable data, use a distinct annotation type not declared on the Annotatable type, and set it with associate_adapter-based paths (annotate with a block)
  3. Read the existing annotation instead of replacing it: MyAnnotation.annotate(o) returns the declared instance
  4. If you truly need different values, declare a new annotation type name rather than redefining the existing one

Example fix

// before
MyAnnotation.annotate_new(o, nil)          # o declares MyAnnotation -> 'attempt to clear'
MyAnnotation.annotate_new(o, {'x' => 2})   # -> 'attempt to redefine'

// after
existing = MyAnnotation.annotate(o)         # read the declared annotation
# mutable per-instance data goes in a separate, undeclared annotation type:
RuntimeInfo.annotate_new(o, {'x' => 2})
Defensive patterns

Strategy: validation

Validate before calling

# only set/clear annotations the type does NOT declare
declared = o.is_a?(Puppet::Pops::Types::Annotatable) && o.annotations.include?(MyAnnotation._pcore_type)
raise ArgumentError, 'annotation is declared on the type; read-only' if declared
MyAnnotation.annotate_new(o, init_hash)

Type guard

def annotation_mutable?(o, ann_class)
  !(o.is_a?(Puppet::Pops::Types::Annotatable) && o.annotations.include?(ann_class._pcore_type))
end

Try / catch

begin
  MyAnnotation.annotate_new(o, init_hash)
rescue ArgumentError => e
  raise DataError, "declared annotation is immutable: #{e.message}" if e.message.start_with?('attempt to')
  raise
end

Prevention

When it happens

Trigger: Calling MyAnnotation.annotate_new(type_declaration, nil) or with a new init hash on an object whose type declaration already carries that annotation; Puppet DSL that mutates annotations on catalog-contained type declarations; code that treats declared annotations like per-instance adapters and tries to overwrite them.

Common situations: Module authors implementing rich data (Pcore) types who attempt to clear/redefine annotations during catalog compilation or rich-data round-trips; serialization code that annotates on load and re-annotates on save; confusion between per-object adapters (safe to set/clear) and type-declared annotations (protected).

Related errors


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