{"record":{"id":"338369b400adc39a","repo":"puppetlabs/puppet","slug":"option-arg-specified-multiple-times","errorCode":null,"errorMessage":"option '%{arg}' specified multiple times","messagePattern":"option '%(.+?)' specified multiple times","errorType":"exception","errorClass":"CommandlineError","httpStatus":null,"severity":"error","filePath":"lib/puppet/util/command_line/trollop.rb","lineNumber":369,"sourceCode":"                  partial_match = @long[::Regexp.last_match(1).tr('-', '_')] || @long[::Regexp.last_match(1).tr('_', '-')] || @long[\"[no-]#{::Regexp.last_match(1).tr('-', '_')}\"] || @long[\"[no-]#{::Regexp.last_match(1).tr('_', '-')}\"]\n                  if partial_match\n                    Puppet.deprecation_warning _(\"Partial argument match detected: correct argument is %{partial_match}, got %{arg}. Partial argument matching is deprecated and will be removed in a future release.\") % { arg: arg, partial_match: partial_match }\n                  end\n                  partial_match\n                else\n                  possible_match\n                end\n              else\n                raise CommandlineError, _(\"invalid argument syntax: '%{arg}'\") % { arg: arg }\n              end\n\n        unless sym\n          next 0 if ignore_invalid_options\n          raise CommandlineError, _(\"unknown argument '%{arg}'\") % { arg: arg } unless sym\n        end\n\n        if given_args.include?(sym) && !@specs[sym][:multi]\n          raise CommandlineError, _(\"option '%{arg}' specified multiple times\") % { arg: arg }\n        end\n\n        given_args[sym] ||= {}\n\n        given_args[sym][:arg] = arg\n        given_args[sym][:params] ||= []\n\n        # The block returns the number of parameters taken.\n        num_params_taken = 0\n\n        unless params.nil?\n          if SINGLE_ARG_TYPES.include?(@specs[sym][:type])\n            given_args[sym][:params] << params[0, 1]  # take the first parameter\n            num_params_taken = 1\n          elsif MULTI_ARG_TYPES.include?(@specs[sym][:type])\n            given_args[sym][:params] << params        # take all the parameters\n            num_params_taken = params.size\n          end","sourceCodeStart":351,"sourceCodeEnd":387,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/util/command_line/trollop.rb#L351-L387","documentation":"While resolving tokens, parse records each option symbol in given_args; if the same symbol appears again and the option was not declared with :multi => true, parse raises CommandlineError at trollop.rb:369 instead of applying last-wins semantics. Multi options accumulate values; single options treat a repeat as a user error. PuppetOptionParser registers puppet's own options with :multi => true (puppet_option_parser.rb:57), so repeats of core puppet flags are allowed and the last value wins — this error mainly affects tools and faces that call the vendored Trollop directly.","triggerScenarios":"`mytool --server a --server b` where :server was declared without :multi; a wrapper script appending a default flag onto a user-supplied ARGV that already contains it; an alias or config layer re-invoking the same option.","commonSituations":"Wrapper scripts that blindly append default flags; users overriding a value already injected by a profile or environment; combining config-file and CLI sources that map to the same option.","solutions":["Pass the option once with the desired value","Declare the option with `:multi => true` if repeats should be accepted (values accumulate, or last wins for single-arg types)","De-duplicate the option in your wrapper or ARGV pre-processing before invoking the command"],"exampleFix":"# before\nopt :server, 'Server host', type: :string   # no :multi\n$ mytool --server a --server b -> raise\n\n# after\nopt :server, 'Server host', type: :string, multi: true","handlingStrategy":"validation","validationCode":"# De-duplicate single-shot options in argv before parsing\nseen = {}\ncleaned = []\nvalue_taking = ->(t) { t =~ /^--\\S+=$/ || t =~ /^--no-/ || t == '--' }   # adjust to your option set\ntokens = argv.dup\nuntil tokens.empty?\n  t = tokens.shift\n  if (m = t.match(/^--([^=]+)(?:=(.*))?$/)) && !multi_options.include?(m[1].tr('-', '_').to_sym)\n    cleaned.reject! { |c| c =~ /^--#{m[1]}=/ }   # last wins, like explicit :multi\n    cleaned << t\n    cleaned << tokens.shift if m[2].nil? && next_is_value(tokens.first)   # keep its value\n  else\n    cleaned << t\n  end\nend","typeGuard":null,"tryCatchPattern":"begin\n  opts = parser.parse(argv)\nrescue Puppet::Util::CommandLine::Trollop::CommandlineError => e\n  raise unless e.message.include?('specified multiple times')\n  opts = parser.parse(argv.each_with_object({}) { |t, h| (h[t[/^--[^=]+/] || t] ||= []) << t }.values.map(&:last))\nend","preventionTips":["Pass each single-shot option exactly once; put overrides in one place (wrapper, config, or CLI - not several)","Declare repeatable options with multi: true when repeats are legitimate","In wrapper scripts, check whether a flag is already present before appending it"],"tags":["puppet","trollop","cli","option-parsing","duplicate"],"backgroundTag":"duplicate-cli-option","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}