puppetlabs/puppet · error · ArgumentError

short option name %{value0} is already taken; please specify

Error message

short option name %{value0} is already taken; please specify a (different) :short

What it means

The parser keeps an @short registry mapping single characters to option symbols. Once :short is normalized to a character, opt raises ArgumentError at trollop.rb:243 if that character is already claimed by another option. Matching is exact per character, so 'v' and 'V' are distinct and can coexist. PuppetOptionParser registers its options without :short, so inside the puppet executable this only fires on explicit duplicate declarations.

Source

Thrown at lib/puppet/util/command_line/trollop.rb:243

        when /^[^-]/
          opts[:long]
        else
          raise ArgumentError, _("invalid long option name %{name}") % { name: opts[:long].inspect }
        end
      raise ArgumentError, _("long option name %{value0} is already taken; please specify a (different) :long") % { value0: opts[:long].inspect } if @long[opts[:long]]

      ## fill in :short
      unless opts[:short] == :none
        opts[:short] = opts[:short].to_s if opts[:short]
      end
      opts[:short] = case opts[:short]
                     when /^-(.)$/; ::Regexp.last_match(1)
                     when nil, :none, /^.$/; opts[:short]
                     else raise ArgumentError, _("invalid short option name '%{name}'") % { name: opts[:short].inspect }
                     end

      if opts[:short]
        raise ArgumentError, _("short option name %{value0} is already taken; please specify a (different) :short") % { value0: opts[:short].inspect } if @short[opts[:short]]
        raise ArgumentError, _("a short option name can't be a number or a dash") if opts[:short] =~ INVALID_SHORT_ARG_REGEX
      end

      ## fill in :default for flags
      opts[:default] = false if opts[:type] == :flag && opts[:default].nil?

      ## autobox :default for :multi (multi-occurrence) arguments
      opts[:default] = [opts[:default]] if opts[:default] && opts[:multi] && !opts[:default].is_a?(Array)

      ## fill in :multi
      opts[:multi] ||= false

      opts[:desc] ||= desc
      @long[opts[:long]] = name
      @short[opts[:short]] = name if opts[:short] && opts[:short] != :none
      @specs[name] = opts
      @order << [:opt, name]
    end

View on GitHub (pinned to e227c27540)

Solutions

  1. Give the second option a different, unused letter
  2. Set `short: :none` on one of the two options
  3. Leave :short unset on one option so Trollop derives a free letter from its long name

Example fix

# before
opt :verbose, 'Verbose', short: 'v'
opt :version, 'Version', short: 'v'   # 'v' already taken -> raise

# after
opt :verbose, 'Verbose', short: 'v'
opt :version, 'Version', short: 'V'
Defensive patterns

Strategy: validation

Validate before calling

# Track claimed short characters and check before declaring
claimed = {}   # char => option name
char = opts[:short].to_s.sub(/^-/, '')
if claimed.key?(char)
  raise ArgumentError, "short '-#{char}' already used by #{claimed[char]}"
end
claimed[char] = name unless char.empty? || opts[:short] == :none

Try / catch

begin
  parser.opt name, desc, short: candidate
rescue ArgumentError => e
  raise unless e.message.include?('short option name') && e.message.include?('already taken')
  parser.opt name, desc, short: :none
end

Prevention

When it happens

Trigger: Two options both declared with `short: 'v'`; reopening a face/application whose option already claimed the letter; a shared option module declaring the same letter in two parsers built from the same spec list.

Common situations: Monkey-patching an existing CLI whose letter is taken; copy-pasting option declarations between faces without checking the letters already in use.

Related errors


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