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)
endView on GitHub (pinned to e227c27540)
Solutions
- Delete the action-level declaration and keep the face-level one (it applies to every action)
- Or keep it action-local and remove the face-level declaration
- 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
- Declare an option either at face level or on an action, never both
- When promoting an action option to the face, delete the action-level copy in the same commit
- After declaring face-level options, scan `actions.each { |a| a.options }` for overlaps in tests
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
- Option %{option} conflicts with existing option %{conflict}
- %{name} can't be optional and have a default value
- default value for %{name} is a %{class_name}, not a proc
- before action hook for %{name} is a %{class_name}, not a pro
- after action hook for %{name} is a %{class_name}, not a proc
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/2cce9ed13a9c554c.
Report an issue: GitHub.