puppetlabs/puppet · error · ArgumentError

Option %{option} conflicts with existing option %{conflict}

Error message

Option %{option} conflicts with existing option %{conflict} on %{action}

What it means

The second collision check in add_option: a new face-level option is compared against options registered on each of the face's actions (lib/puppet/interface/option_manager.rb:68). If any action already owns an option with the same alias, registration fails, naming the conflicting options and the action. Face-level options apply to all actions, so a clash with even one action's option is fatal.

Source

Thrown at lib/puppet/interface/option_manager.rb:68

  # @api private
  def add_option(option)
    # @options collects the added options in the order they're declared.
    # @options_hash collects the options keyed by alias for quick lookups.
    @options      ||= []
    @options_hash ||= {}

    option.aliases.each do |name|
      conflict = get_option(name)
      if conflict
        raise ArgumentError, _("Option %{option} conflicts with existing option %{conflict}") %
                             { option: option, conflict: conflict }
      end

      actions.each do |action|
        action = get_action(action)
        conflict = action.get_option(name)
        if conflict
          raise ArgumentError, _("Option %{option} conflicts with existing option %{conflict} on %{action}") %
                               { option: option, conflict: conflict, action: action }
        end
      end
    end

    @options << option.name

    option.aliases.each do |name|
      @options_hash[name] = option
    end

    option
  end

  # @api private
  def options
    walk_inheritance_tree(@options, :options)
  end

View on GitHub (pinned to e227c27540)

Solutions

  1. Delete the action-level declaration and keep the face-level one (it applies to every action)
  2. Or keep it action-local and remove the face-level declaration
  3. If semantics genuinely differ, rename one variant (e.g. --force-local)

Example fix

# before
action :run do
  option "--force" do
    summary "Skip safety checks"
  end
end
option "--force" do
  summary "Force operation"
end

# after
# face level only; delete the copy inside action :run
option "--force" do
  summary "Force operation"
end
Defensive patterns

Strategy: validation

Validate before calling

# inside the define block, before a face-level option
actions.each do |a|
  if get_action(a).get_option(:force)
    raise ArgumentError, "action #{a} already owns option --force"
  end
end
option '--force' do
  summary 'Force operation'
end

Prevention

When it happens

Trigger: `action(:run) { option '--force' do ... end }` followed by a face-level `option '--force' do ... end`; promoting an action option to face level without deleting the action-level copy.

Common situations: Refactoring a shared flag up to the face and forgetting the per-action declaration; two developers adding the same flag at different levels.

Related errors


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