pytest-dev/pytest · error · ValueError

lowercase short options are reserved

Error message

lowercase short options are reserved

What it means

pytest reserves single-letter lowercase short options (e.g. -x, -q) for its own core. When a plugin author registers an option via the public group.addoption() (not the internal _addoption) and one of the option strings is a lowercase short flag like '-x', pytest rejects it. This keeps the short namespace stable for pytest itself.

Solutions

  1. Use an uppercase short option (e.g. '-X') instead, since uppercase shorts are not reserved.
  2. Use only a long option (e.g. '--myflag') and drop the short form entirely.
  3. If you genuinely need a core short option, you cannot register it; rely on the existing core behavior instead.

Example fix

// before
group.addoption('-x', '--my-x-flag', action='store_true')
// after
group.addoption('-X', '--my-x-flag', action='store_true')
Defensive patterns

Strategy: validation

Validate before calling

def is_reserved_short(opt: str) -> bool:
    return len(opt) >= 2 and opt[0] == '-' and opt[1].islower()

for o in opts:
    assert not is_reserved_short(o), f'{o!r} is a reserved lowercase short option'

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: Calling group.addoption('-x', ...) (lowercase single letter) from a conftest or plugin. Only paths through addoption()/allow_reserved=False enforce this; pytest core uses _addoption which sets allow_reserved=True.

Common situations: Plugin author picking '-c' or '-f' for their flag without realizing it collides with reserved core options; a conftest trying to add a convenience short flag.

Related errors


AI-assisted analysis of pytest-dev/pytest@0d6fbdeffa (2026-08-11). Data as JSON: /api/errors/c2c50d5b5c13cc5d. Report an issue: GitHub.

Appendix: source

Thrown at src/_pytest/config/argparsing.py:483

                            f"option dest {dest!r} already used by "
                            f"{option.names()!r} (this is the option that maps to "
                            f"dest {dest!r}); pass dest={dest!r} explicitly "
                            "to share the destination"
                        )
        self._addoption_inner(opts, attrs, allow_reserved=False)

    def _addoption(self, *opts: str, **attrs: Any) -> None:
        """Like addoption(), but also allows registering short lower case options (e.g. -x),
        which are reserved for pytest core."""
        self._addoption_inner(opts, attrs, allow_reserved=True)

    def _addoption_inner(
        self, opts: tuple[str, ...], attrs: dict[str, Any], allow_reserved: bool
    ) -> None:
        if not allow_reserved:
            for opt in opts:
                if len(opt) >= 2 and opt[0] == "-" and opt[1].islower():
                    raise ValueError("lowercase short options are reserved")

        action = self._arggroup.add_argument(*opts, **attrs)
        option = Argument(action)
        self.options.append(option)
        if self.parser:
            for name in option.names():
                self.parser._opt2dest[name] = option.dest
            self.parser.processoption(option)


class PytestArgumentParser(argparse.ArgumentParser):
    def __init__(
        self,
        usage: str | None,
        extra_info: dict[str, str],
        *,
        prog: str | None = None,
    ) -> None:

View on GitHub (pinned to 0d6fbdeffa)