puppetlabs/puppet · error · Puppet::ParseError
%{member} attempts to override final %{label}
Error message
%{member} attempts to override final %{label} What it means
Members declared final => true in an Object type are closed for extension: a child type that attempts to override one gets a Puppet::ParseError. The only exception in the source is a final constant overridden by another constant (constant? && member.constant?), since constants are resolved statically.
Source
Thrown at lib/puppet/pops/types/p_object_type.rb:177
end
self
else
parent_member.assert_can_be_overridden(self)
end
end
# Checks if the given _member_ can override this member.
#
# @param member [PAnnotatedMember] the overriding member
# @return [PAnnotatedMember] its argument
# @raises [Puppet::ParseError] if the assertion fails
# @api private
def assert_can_be_overridden(member)
unless instance_of?(member.class)
raise Puppet::ParseError, _("%{member} attempts to override %{label}") % { member: member.label, label: label }
end
if @final && !(constant? && member.constant?)
raise Puppet::ParseError, _("%{member} attempts to override final %{label}") % { member: member.label, label: label }
end
unless member.override?
# TRANSLATOR 'override => true' is a puppet syntax and should not be translated
raise Puppet::ParseError, _("%{member} attempts to override %{label} without having override => true") % { member: member.label, label: label }
end
unless @type.assignable?(member.type)
raise Puppet::ParseError, _("%{member} attempts to override %{label} with a type that does not match") % { member: member.label, label: label }
end
member
end
def constant?
false
end
# @return [Boolean] `true` if this feature cannot be overridden
# @api publicView on GitHub (pinned to e227c27540)
Solutions
- Do not override the final member — add a differently named member in the child type instead.
- Use composition: wrap the parent type in a new Object type rather than inheriting from it.
- If you own the parent type, remove 'final' => true there (accepting that all subclasses see the contract change).
Example fix
# before — Base declares 'id' with final => true
type Sub = Object[{'parent' => 'Base', 'attributes' => {'id' => {'type' => String, 'override' => true}}}]
# after
type Sub = Object[{'parent' => 'Base', 'attributes' => {'sub_id' => String}}] Defensive patterns
Strategy: validation
Validate before calling
# Ruby — before generating an override, check the inherited member is not final
inherited = parent_members[name]
fail("#{name} is final and cannot be overridden") if inherited&.final? && !(inherited.constant? && entry['kind'] == 'constant') Type guard
def overridable_member?(member) !member.final? || (member.constant? && member.constant?) # constant-over-constant is the only exception end
Try / catch
begin
Puppet::Pops::Types::PObjectType.new(name, hash)
rescue Puppet::ParseError => e
fail("cannot extend final member: #{e.message} — use a new attribute name instead")
end Prevention
- Treat final as a published contract freeze; plan composition instead of inheritance for such types.
- When upgrading dependencies, diff final flags on parent types your types extend.
When it happens
Trigger: Parent attribute declared with 'final' => true; child type declares an attribute with the same name and 'override' => true. The final check fires before the override-flag and type checks.
Common situations: Extending third-party or core data types whose authors marked members final to freeze the contract; upstream version upgrades that newly mark a member final and break previously-working child types.
Related errors
- expected %{label} to override an inherited %{feature_type},
- %{member} attempts to override %{label}
- %{member} attempts to override %{label} without having overr
- %{member} attempts to override %{label} with a type that doe
- reference to unresolved type '%{name}'
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/313b39e4cf5775e7.
Report an issue: GitHub.