From 9e0eed1f42728eb406272db416a7362d19704235 Mon Sep 17 00:00:00 2001 From: Richard Si Date: Sat, 25 Jul 2026 16:06:29 -0400 Subject: [PATCH 1/2] fix: Disable rich interactivity when running in CI --- news/13354.bugfix.rst | 2 ++ src/pip/_internal/network/session.py | 30 +--------------------------- src/pip/_internal/utils/logging.py | 13 +++++++++--- src/pip/_internal/utils/misc.py | 28 ++++++++++++++++++++++++++ tests/unit/test_network_session.py | 2 +- 5 files changed, 42 insertions(+), 33 deletions(-) create mode 100644 news/13354.bugfix.rst diff --git a/news/13354.bugfix.rst b/news/13354.bugfix.rst new file mode 100644 index 0000000000..95c94e2356 --- /dev/null +++ b/news/13354.bugfix.rst @@ -0,0 +1,2 @@ +Stop animating progress bars and status spinners when running on CI, even +if ``FORCE_COLOR`` is set. diff --git a/src/pip/_internal/network/session.py b/src/pip/_internal/network/session.py index dbb4113297..5ddbb2ae34 100644 --- a/src/pip/_internal/network/session.py +++ b/src/pip/_internal/network/session.py @@ -46,6 +46,7 @@ from pip._internal.utils.glibc import libc_ver from pip._internal.utils.misc import ( build_url_from_netloc, + looks_like_ci, parse_netloc, redact_auth_from_url, ) @@ -79,35 +80,6 @@ ] -# These are environment variables present when running under various -# CI systems. For each variable, some CI systems that use the variable -# are indicated. The collection was chosen so that for each of a number -# of popular systems, at least one of the environment variables is used. -# This list is used to provide some indication of and lower bound for -# CI traffic to PyPI. Thus, it is okay if the list is not comprehensive. -# For more background, see: https://github.com/pypa/pip/issues/5499 -CI_ENVIRONMENT_VARIABLES = ( - # Azure Pipelines - "BUILD_BUILDID", - # Jenkins - "BUILD_ID", - # AppVeyor, CircleCI, Codeship, Gitlab CI, Shippable, Travis CI - "CI", - # Explicit environment variable. - "PIP_IS_CI", -) - - -def looks_like_ci() -> bool: - """ - Return whether it looks like pip is running under CI. - """ - # We don't use the method of checking for a tty (e.g. using isatty()) - # because some CI systems mimic a tty (e.g. Travis CI). Thus that - # method doesn't provide definitive information in either direction. - return any(name in os.environ for name in CI_ENVIRONMENT_VARIABLES) - - @functools.lru_cache(maxsize=1) def user_agent() -> str: """ diff --git a/src/pip/_internal/utils/logging.py b/src/pip/_internal/utils/logging.py index e067703580..8892e14d61 100644 --- a/src/pip/_internal/utils/logging.py +++ b/src/pip/_internal/utils/logging.py @@ -29,7 +29,7 @@ from pip._internal.utils._log import VERBOSE, getLogger from pip._internal.utils.compat import WINDOWS from pip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX -from pip._internal.utils.misc import StreamWrapper, ensure_dir +from pip._internal.utils.misc import StreamWrapper, ensure_dir, looks_like_ci _log_state = threading.local() _stdout_console = None @@ -320,8 +320,15 @@ def setup_logging(verbosity: int, no_color: bool, user_log_file: str | None) -> ["user_log"] if include_user_log else [] ) global _stdout_console, stderr_console - _stdout_console = PipConsole(file=sys.stdout, no_color=no_color, soft_wrap=True) - _stderr_console = PipConsole(file=sys.stderr, no_color=no_color, soft_wrap=True) + console_options: dict[str, Any] = {"no_color": no_color, "soft_wrap": True} + if looks_like_ci(): + # A lot of CI workflows use FORCE_COLOR and similar environment + # variables so their CI output remains colorized, but this has + # the unfortunate consequence of rendering progress bars that + # (often) span many lines. + console_options["force_interactive"] = False + _stdout_console = PipConsole(file=sys.stdout, **console_options) + _stderr_console = PipConsole(file=sys.stderr, **console_options) logging.config.dictConfig( { diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index ca304e9e28..c676029481 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -52,6 +52,7 @@ "ensure_dir", "remove_auth_from_url", "check_externally_managed", + "looks_like_ci", "ConfiguredBuildBackendHookCaller", ] @@ -65,6 +66,23 @@ OnErr = Callable[[FunctionType, Path, ExcInfo], Any] FILE_CHUNK_SIZE = 1024 * 1024 +# These are environment variables present when running under various +# CI systems. For each variable, some CI systems that use the variable +# are indicated. The collection was chosen so that for each of a number +# of popular systems, at least one of the environment variables is used. +# This list is used to provide some indication of and lower bound for +# CI traffic to PyPI. Thus, it is okay if the list is not comprehensive. +# For more background, see: https://github.com/pypa/pip/issues/5499 +CI_ENVIRONMENT_VARIABLES = ( + # Azure Pipelines + "BUILD_BUILDID", + # Jenkins + "BUILD_ID", + # AppVeyor, CircleCI, Codeship, Gitlab CI, Shippable, Travis CI + "CI", + # Explicit environment variable. + "PIP_IS_CI", +) def get_pip_version() -> str: @@ -785,3 +803,13 @@ def warn_if_run_as_root() -> None: "Use the --root-user-action option if you know what you are doing and " "want to suppress this warning." ) + + +def looks_like_ci() -> bool: + """ + Return whether it looks like pip is running under CI. + """ + # We don't use the method of checking for a tty (e.g. using isatty()) + # because some CI systems mimic a tty (e.g. Travis CI). Thus that + # method doesn't provide definitive information in either direction. + return any(name in os.environ for name in CI_ENVIRONMENT_VARIABLES) diff --git a/tests/unit/test_network_session.py b/tests/unit/test_network_session.py index bcae8f47fb..d025ffefb5 100644 --- a/tests/unit/test_network_session.py +++ b/tests/unit/test_network_session.py @@ -34,11 +34,11 @@ ) from pip._internal.models.link import Link from pip._internal.network.session import ( - CI_ENVIRONMENT_VARIABLES, HTTPAdapter, PipSession, user_agent, ) +from pip._internal.utils.misc import CI_ENVIRONMENT_VARIABLES from tests.lib.output import render_to_text from tests.lib.server import make_mock_server, server_running From f2ea9a77969f91434789eaa95adb511be113d916 Mon Sep 17 00:00:00 2001 From: Richard Si Date: Sun, 26 Jul 2026 23:39:18 -0400 Subject: [PATCH 2/2] Reword comment --- src/pip/_internal/utils/logging.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pip/_internal/utils/logging.py b/src/pip/_internal/utils/logging.py index 8892e14d61..233ce4e0af 100644 --- a/src/pip/_internal/utils/logging.py +++ b/src/pip/_internal/utils/logging.py @@ -322,10 +322,11 @@ def setup_logging(verbosity: int, no_color: bool, user_log_file: str | None) -> global _stdout_console, stderr_console console_options: dict[str, Any] = {"no_color": no_color, "soft_wrap": True} if looks_like_ci(): - # A lot of CI workflows use FORCE_COLOR and similar environment - # variables so their CI output remains colorized, but this has - # the unfortunate consequence of rendering progress bars that - # (often) span many lines. + # Don't animate progress bars and status spinners when running + # in CI. A lot of CI workflows use FORCE_COLOR and similar envvars + # so their CI output remains colorized, but this causes rich to + # assume a fully interactive terminal, resulting in any animated + # output to span many lines which is distracting (and unhelpful). console_options["force_interactive"] = False _stdout_console = PipConsole(file=sys.stdout, **console_options) _stderr_console = PipConsole(file=sys.stderr, **console_options)