pypa/pip · error · ConfigurationError

Fatal Internal error [id=2]. Please report as a bug.

Error message

Fatal Internal error [id=2]. Please report as a bug.

What it means

Raised by Configuration._get_parser_to_modify() when the list of parsers for the selected load_only layer is empty. This means pip was asked to edit a configuration layer (e.g. --site) but no config file for that layer was ever loaded/parsed — there is no file object to write to. pip treats this as an internal inconsistency because the load path should always populate at least one parser entry.

Solutions

  1. Report as a bug to pip with the command, version, and config file layout (https://github.com/pypa/pip/issues).
  2. Ensure the target config file for the selected scope actually exists or is creatable (check home directory and sys.prefix writability).
  3. Retry with a different scope (--user vs --global) to see if the issue is layer-specific.
  4. Restart the interpreter/shell to rule out stale patched state.
Defensive patterns

Strategy: try-catch

Try / catch

from pip._internal.exceptions import ConfigurationError
try:
    cfg.set_value(key, value)
except ConfigurationError as e:
    if 'Fatal Internal error [id=2]' in str(e):
        # reload config files, then retry once
        cfg.load()
        cfg.set_value(key, value)
    else:
        raise

Prevention

When it happens

Trigger: A genuine internal inconsistency: load_only is set, but _load_config_files did not register any parser for that variant. Practically unreachable without a bug in the load logic, a failed/patched _load_file, or a race that clears the _parsers list.

Common situations: Extremely rare; could surface with custom test patches of iter_config_files or _load_file, or version skew in embedded pip usage. Most end users never encounter it.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/5110c3c458b5094d. Report an issue: GitHub.

Appendix: source

Thrown at src/pip/_internal/configuration.py:381

        # virtualenv config
        yield kinds.SITE, config_files[kinds.SITE]

        if env_config_file is not None:
            yield kinds.ENV, [env_config_file]
        else:
            yield kinds.ENV, []

    def get_values_in_config(self, variant: Kind) -> dict[str, Any]:
        """Get values present in a config file"""
        return self._config[variant]

    def _get_parser_to_modify(self) -> tuple[str, RawConfigParser]:
        # Determine which parser to modify
        assert self.load_only
        parsers = self._parsers[self.load_only]
        if not parsers:
            # This should not happen if everything works correctly.
            raise ConfigurationError(
                "Fatal Internal error [id=2]. Please report as a bug."
            )

        # Use the highest priority parser.
        return parsers[-1]

    # XXX: This is patched in the tests.
    def _mark_as_modified(self, fname: str, parser: RawConfigParser) -> None:
        file_parser_tuple = (fname, parser)
        if file_parser_tuple not in self._modified_parsers:
            self._modified_parsers.append(file_parser_tuple)

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}({self._dictionary!r})"

View on GitHub (pinned to f399c37189)