puppetlabs/puppet · error · Puppet::DevError

Could not convert change '%{name}' to string: %{detail}

Error message

Could not convert change '%{name}' to string: %{detail}

What it means

When a property changes, Puppet renders the log/report message via change_to_s, which calls is_to_s and should_to_s on the current and desired values. If those helpers raise, the rescue logs the cause (Puppet.log_exception) and re-raises it as Puppet::DevError including the property name and the original detail; genuine Puppet::Error exceptions pass through unchanged. The message you see is therefore a wrapper - the real defect is whatever the detail reports.

Source

Thrown at lib/puppet/property.rb:218

  # Formats a message for a property change from the given `current_value` to the given `newvalue`.
  # @return [String] a message describing the property change.
  # @note If called with equal values, this is reported as a change.
  # @raise [Puppet::DevError] if there were issues formatting the message
  #
  def change_to_s(current_value, newvalue)
    if current_value == :absent
      "defined '#{name}' as #{should_to_s(newvalue)}"
    elsif newvalue == :absent or newvalue == [:absent]
      "undefined '#{name}' from #{is_to_s(current_value)}"
    else
      "#{name} changed #{is_to_s(current_value)} to #{should_to_s(newvalue)}"
    end
  rescue Puppet::Error
    raise
  rescue => detail
    message = _("Could not convert change '%{name}' to string: %{detail}") % { name: name, detail: detail }
    Puppet.log_exception(detail, message)
    raise Puppet::DevError, message, detail.backtrace
  end

  # Produces the name of the event to use to describe a change of this property's value.
  # The produced event name is either the event name configured for this property, or a generic
  # event based on the name of the property with suffix `_changed`, or if the property is
  # `:ensure`, the name of the resource type and one of the suffixes `_created`, `_removed`, or `_changed`.
  # @return [String] the name of the event that describes the change
  #
  def event_name
    value = should

    event_name = self.class.value_option(value, :event) and return event_name

    name == :ensure or return (name.to_s + "_changed").to_sym

    (resource.type.to_s + case value
                          when :present; "_created"
                          when :absent;  "_removed"

View on GitHub (pinned to e227c27540)

Solutions

  1. Read the detail in the message (and the logged original exception) - it names the real failure; fix that munge/should logic.
  2. Make is_to_s/should_to_s defensive: coerce values with to_s/inspect instead of assuming structure.
  3. If you rescue inside those helpers, re-raise Puppet::Error subclasses so they are not double-wrapped.

Example fix

# before (custom property)
def should_to_s(v)
  "#{v[:name]}=#{v[:value]}"   # NoMethodError when v is not a Hash -> DevError wrapper
end

# after
def should_to_s(v)
  v.is_a?(Hash) ? "#{v[:name]}=#{v[:value]}" : v.inspect
end
Defensive patterns

Strategy: try-catch

Try / catch

msg = begin
  property.change_to_s(is_value, should_value)
rescue Puppet::DevError
  "#{property.name} changed #{is_value.inspect} to #{should_value.inspect}"   # fallback text
end

Prevention

When it happens

Trigger: A custom property whose munge, should or should_to_s raises for a specific value; calling change_to_s during report generation when the resource has no provider; values that fail interpolation inside the message helpers.

Common situations: Third-party properties with fragile munge blocks; tooling that formats change messages outside a normal transaction; values of an unexpected class reaching message rendering.

Related errors


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