RustPython/RustPython · error · ArgumentError

unexpected option string: %s

Error message

unexpected option string: %s

What it means

A defensive raise at the bottom of ArgumentParser._parse_optional — the comment literally says 'shouldn't ever get here'. It fires when a token reaches the option parser yet matches none of the known branches: no exact option_strings hit, no permitted abbreviation (allow_abbrev), no '--opt=value' split, not a negative-number-looking arg, and not the '-' sentinel. Encountering it means the parser's option table or prefix configuration is inconsistent with the token being examined.

Source

Thrown at Lib/argparse.py:2516

            option_prefix, sep, explicit_arg = option_string.partition('=')
            if not sep:
                sep = explicit_arg = None
            short_option_prefix = option_string[:2]
            short_explicit_arg = option_string[2:]

            for option_string in self._option_string_actions:
                if option_string == short_option_prefix:
                    action = self._option_string_actions[option_string]
                    tup = action, option_string, '', short_explicit_arg
                    result.append(tup)
                elif self.allow_abbrev and option_string.startswith(option_prefix):
                    action = self._option_string_actions[option_string]
                    tup = action, option_string, sep, explicit_arg
                    result.append(tup)

        # shouldn't ever get here
        else:
            raise ArgumentError(None, _('unexpected option string: %s') % option_string)

        # return the collected option tuples
        return result

    def _get_nargs_pattern(self, action):
        # in all examples below, we have to allow for '--' args
        # which are represented as '-' in the pattern
        nargs = action.nargs
        # if this is an optional action, -- is not allowed
        option = action.option_strings

        # the default (None) is assumed to be a single argument
        if nargs is None:
            nargs_pattern = '([A])' if option else '(-*A-*)'

        # allow zero or one arguments
        elif nargs == OPTIONAL:
            nargs_pattern = '(A?)' if option else '(-*A?-*)'

View on GitHub (pinned to aaeab4f754)

Solutions

  1. Verify every option string was registered through add_argument — never by editing parser._option_string_actions directly
  2. Check that prefix_chars covers the characters your options actually start with, and that options use those prefixes consistently
  3. Reproduce on the latest Python patch release; if it persists, minimize (parser + argv) and file a bug at bugs.python.org
  4. As a stopgap, pre-filter argv tokens that match no registered option before calling parse_args

Example fix

# before
p = argparse.ArgumentParser(prefix_chars='+')
p.add_argument('+verbose', action='store_true')
p.parse_args(['+verbose', '-x'])   # '-x' unrecognized prefix falls through
# after
p = argparse.ArgumentParser(prefix_chars='-+')
p.add_argument('-x', action='store_true')
p.parse_args(['+verbose', '-x'])
Defensive patterns

Strategy: try-catch

Validate before calling

def known_tokens_only(parser, argv):
    opts = set(parser._option_string_actions)
    out = []
    for t in argv:
        if t.startswith(tuple(parser.prefix_chars)) and t not in opts and t != '--':
            continue  # drop tokens the parser cannot classify
        out.append(t)
    return out

Try / catch

try:
    args = parser.parse_args(argv)
except argparse.ArgumentError as e:
    if str(e).startswith('unexpected option string'):
        print('internal parser inconsistency:', e, file=sys.stderr)
        raise SystemExit(2)
    raise

Prevention

When it happens

Trigger: Tokens starting with prefix_chars that fall through every branch — e.g. parsers built with unusual prefix_chars (prefix_chars='+') receiving '-like' tokens; option tables mutated behind argparse's back (manual edits to parser._option_string_actions); monkeypatched parsers in test suites that leave the registry half-updated.

Common situations: Custom-prefix CLIs (options like '+verbose'); test harnesses patching argparse internals; copies of parsers made via copy/deepcopy that share state inconsistently; very rarely a genuine CPython regression worth reporting upstream.

Related errors


AI-assisted analysis of RustPython/RustPython@aaeab4f754 (2026-08-17). Data as JSON: /api/errors/3b2222b6fe4ab588. Report an issue: GitHub.