pypa/pip · error · ValueError

Invalid value: on_returncode={on_returncode!r}

Error message

Invalid value: on_returncode={on_returncode!r}

What it means

Defensive ValueError raised by pip's subprocess runner when the `on_returncode` argument is not one of the three allowed literals: 'raise', 'warn', or 'ignore'. It fires only when a subprocess the runner invoked actually exits non-zero and the supplied handling mode is unrecognized. The default is 'raise', so this only happens for callers passing a custom bad value.

Source

Thrown at src/pip/_internal/utils/subprocess.py:224

                )
                subprocess_logger.verbose(
                    "[bold magenta]cwd[/]: %s",
                    escape(cwd or "[inherit]"),
                    extra={"markup": True},
                )

            raise error
        elif on_returncode == "warn":
            subprocess_logger.warning(
                'Command "%s" had error code %s in %s',
                command_desc,
                proc.returncode,
                cwd,
            )
        elif on_returncode == "ignore":
            pass
        else:
            raise ValueError(f"Invalid value: on_returncode={on_returncode!r}")
    return output


def runner_with_spinner_message(message: str) -> Callable[..., None]:
    """Provide a subprocess_runner that shows a spinner message.

    Intended for use with for BuildBackendHookCaller. Thus, the runner has
    an API that matches what's expected by BuildBackendHookCaller.subprocess_runner.
    """

    def runner(
        cmd: list[str],
        cwd: str | None = None,
        extra_environ: Mapping[str, Any] | None = None,
    ) -> None:
        with open_spinner(message) as spinner:
            call_subprocess(
                cmd,

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Pass one of exactly 'raise', 'warn', or 'ignore' for on_returncode.
  2. If you need custom handling, leave on_returncode='raise' and catch the resulting InstallationSubprocessError yourself.
  3. Audit third-party plugins for typos in the on_returncode argument.

Example fix

// before
runner.run(cmd, on_returncode="abort")

// after
runner.run(cmd, on_returncode="raise")
Defensive patterns

Strategy: type-guard

Validate before calling

ALLOWED = {'raise', 'warn', 'ignore'}
assert on_returncode in ALLOWED, f'on_returncode must be one of {ALLOWED}'
runner.run(cmd, on_returncode=on_returncode)

Type guard

from typing import Literal

def is_valid_returncode(x: object) -> bool:
    return x in ('raise', 'warn', 'ignore')

ValidReturnCode = Literal['raise', 'warn', 'ignore']

Try / catch

null

Prevention

When it happens

Trigger: Calling pip's internal subprocess runner (e.g. via BuildBackendHookCaller or a custom call site) with on_returncode set to a typo like 'raises', 'abort', 'fail', None, or an unexpected Enum. Only triggers if the subprocess actually returns non-zero.

Common situations: Third-party code subclassing or reusing pip's runner with a typo; passing a value intended for a different pip version's API.

Related errors


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/6330065c40be809a.json. Report an issue: GitHub.