{"id":"c2c50d5b5c13cc5d","repo":"pytest-dev/pytest","slug":"lowercase-short-options-are-reserved","errorCode":null,"errorMessage":"lowercase short options are reserved","messagePattern":"lowercase short options are reserved","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/_pytest/config/argparsing.py","lineNumber":483,"sourceCode":"                            f\"option dest {dest!r} already used by \"\n                            f\"{option.names()!r} (this is the option that maps to \"\n                            f\"dest {dest!r}); pass dest={dest!r} explicitly \"\n                            \"to share the destination\"\n                        )\n        self._addoption_inner(opts, attrs, allow_reserved=False)\n\n    def _addoption(self, *opts: str, **attrs: Any) -> None:\n        \"\"\"Like addoption(), but also allows registering short lower case options (e.g. -x),\n        which are reserved for pytest core.\"\"\"\n        self._addoption_inner(opts, attrs, allow_reserved=True)\n\n    def _addoption_inner(\n        self, opts: tuple[str, ...], attrs: dict[str, Any], allow_reserved: bool\n    ) -> None:\n        if not allow_reserved:\n            for opt in opts:\n                if len(opt) >= 2 and opt[0] == \"-\" and opt[1].islower():\n                    raise ValueError(\"lowercase short options are reserved\")\n\n        action = self._arggroup.add_argument(*opts, **attrs)\n        option = Argument(action)\n        self.options.append(option)\n        if self.parser:\n            for name in option.names():\n                self.parser._opt2dest[name] = option.dest\n            self.parser.processoption(option)\n\n\nclass PytestArgumentParser(argparse.ArgumentParser):\n    def __init__(\n        self,\n        usage: str | None,\n        extra_info: dict[str, str],\n        *,\n        prog: str | None = None,\n    ) -> None:","sourceCodeStart":465,"sourceCodeEnd":501,"githubUrl":"https://github.com/pytest-dev/pytest/blob/98b357f69e380da908740a212288d73b2ee06687/src/_pytest/config/argparsing.py#L465-L501","documentation":"Raised by OptionGroup.addoption() (the public API) when a registered option uses a lowercase single-character short form like '-x'. Lowercase short options are reserved for pytest's own core options; plugins and conftests must use uppercase short options (e.g. '-X') or long options only. The internal _addoption path bypasses this check for core use.","triggerScenarios":"A conftest.py or plugin calling group.addoption('-x', '--extra', ...) where '-x' is a lowercase short option. The check at line 480-483 flags any opt of length >= 2 starting with '-' whose second char is lowercase.","commonSituations":"Plugin authors unaware of the reservation policy; porting an argparse-based tool's options into pytest and keeping lowercase shorts; accidental lowercase letters in short option strings.","solutions":["Use an uppercase short option: addoption('-X', '--extra').","Drop the short form entirely and keep only the long option: addoption('--extra').","Reserve any genuinely needed lowercase short option by coordinating with pytest core."],"exampleFix":"# before\ngroup.addoption('-x', '--extra', help='...')\n\n# after\ngroup.addoption('-X', '--extra', help='...')","handlingStrategy":"validation","validationCode":"def validate_short_option(opt: str) -> None:\n    if len(opt) >= 2 and opt[0] == '-' and opt[1].islower():\n        raise ValueError(f'lowercase short option {opt!r} is reserved')","typeGuard":"def is_reserved_lowercase_short(opt: str) -> bool:\n    return len(opt) >= 2 and opt[0] == '-' and opt[1].islower()","tryCatchPattern":null,"preventionTips":["Use uppercase short options (e.g. -X) or long-only options in plugins.","Reserve lowercase shorts for pytest core."],"tags":["pytest","plugin","config","addoption","cli","naming-convention"],"analyzedSha":"98b357f69e380da908740a212288d73b2ee06687","analyzedAt":"2026-08-04T20:26:34.442Z","schemaVersion":2}