{"record":{"id":"2cce9ed13a9c554c","repo":"puppetlabs/puppet","slug":"option-option-conflicts-with-existing-option-2cce9e","errorCode":null,"errorMessage":"Option %{option} conflicts with existing option %{conflict} on %{action}","messagePattern":"Option %(.+?) conflicts with existing option %(.+?) on %(.+?)","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/puppet/interface/option_manager.rb","lineNumber":68,"sourceCode":"  # @api private\n  def add_option(option)\n    # @options collects the added options in the order they're declared.\n    # @options_hash collects the options keyed by alias for quick lookups.\n    @options      ||= []\n    @options_hash ||= {}\n\n    option.aliases.each do |name|\n      conflict = get_option(name)\n      if conflict\n        raise ArgumentError, _(\"Option %{option} conflicts with existing option %{conflict}\") %\n                             { option: option, conflict: conflict }\n      end\n\n      actions.each do |action|\n        action = get_action(action)\n        conflict = action.get_option(name)\n        if conflict\n          raise ArgumentError, _(\"Option %{option} conflicts with existing option %{conflict} on %{action}\") %\n                               { option: option, conflict: conflict, action: action }\n        end\n      end\n    end\n\n    @options << option.name\n\n    option.aliases.each do |name|\n      @options_hash[name] = option\n    end\n\n    option\n  end\n\n  # @api private\n  def options\n    walk_inheritance_tree(@options, :options)\n  end","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/interface/option_manager.rb#L50-L86","documentation":"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.","triggerScenarios":"`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.","commonSituations":"Refactoring a shared flag up to the face and forgetting the per-action declaration; two developers adding the same flag at different levels.","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)"],"exampleFix":"# before\naction :run do\n  option \"--force\" do\n    summary \"Skip safety checks\"\n  end\nend\noption \"--force\" do\n  summary \"Force operation\"\nend\n\n# after\n# face level only; delete the copy inside action :run\noption \"--force\" do\n  summary \"Force operation\"\nend","handlingStrategy":"validation","validationCode":"# inside the define block, before a face-level option\nactions.each do |a|\n  if get_action(a).get_option(:force)\n    raise ArgumentError, \"action #{a} already owns option --force\"\n  end\nend\noption '--force' do\n  summary 'Force operation'\nend","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["puppet","faces","option","name-collision","argumenterror"],"backgroundTag":"duplicate-option-declaration","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}