[3.14] gh-153970: Fix str() of CalledProcessError when returncode is not an integer (gh-153971) (gh-155924)

corona10 <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/bc3972908b47a66bb2ac3b8a38a202ad5e309714
commit: bc3972908b47a66bb2ac3b8a38a202ad5e309714
branch: 3.14
author: Dongjun Shin <[email protected]>
committer: corona10 <[email protected]>
date: 2026-08-17T14:32:58+09:00
summary:

[3.14] gh-153970: Fix str() of CalledProcessError when returncode is not an integer (gh-153971) (gh-155924)

CalledProcessError.__str__() fell through to a branch that formats the
return code with %d, which raises TypeError when returncode is None.
(cherry picked from commit 7c653e2540cbe4180efb3bd83b63865a1f675650)

Co-authored-by: Vyron Vasileiadis <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-07-22-12-00-00.gh-issue-153970.Kq7Wn3.rst
M Doc/library/subprocess.rst
M Lib/subprocess.py
M Lib/test/test_subprocess.py

diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst
index 66a3d6a484a8a8f..4be045d50d54b44 100644
--- a/Doc/library/subprocess.rst
+++ b/Doc/library/subprocess.rst
@@ -239,8 +239,8 @@ underlying :class:`Popen` interface can be used directly.
 
     .. attribute:: returncode
 
-        Exit status of the child process.  If the process exited due to a
-        signal, this will be the negative signal number.
+        Exit status of the child process, an integer.  If the process
+        exited due to a signal, this will be the negative signal number.
 
     .. attribute:: cmd
 
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index 52b7b7117703d71..1ff47af3dde7532 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -143,7 +143,7 @@ def __init__(self, returncode, cmd, output=None, stderr=None):
         self.stderr = stderr
 
     def __str__(self):
-        if self.returncode and self.returncode < 0:
+        if isinstance(self.returncode, int) and self.returncode < 0:
             try:
                 return "Command '%s' died with %r." % (
                         self.cmd, signal.Signals(-self.returncode))
@@ -151,8 +151,8 @@ def __str__(self):
                 return "Command '%s' died with unknown signal %d." % (
                         self.cmd, -self.returncode)
         else:
-            return "Command '%s' returned non-zero exit status %d." % (
-                    self.cmd, self.returncode)
+            return (f"Command '{self.cmd}' returned non-zero "
+                    f"exit status {self.returncode}.")
 
     @property
     def stdout(self):
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index d13f0ec2522abdc..65a3d321f44d117 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -2415,6 +2415,16 @@ def test_CalledProcessError_str_non_zero(self):
         error_string = str(err)
         self.assertIn("non-zero exit status 2.", error_string)
 
+        # returncode which is not an integer, which happens for example when
+        # Popen is mocked: str() must not fail
+        for returncode in (None, "2", 2.5, [2]):
+            with self.subTest(returncode=returncode):
+                err = subprocess.CalledProcessError(returncode, "fake cmd")
+                self.assertEqual(
+                    str(err),
+                    f"Command 'fake cmd' returned non-zero "
+                    f"exit status {returncode}.")
+
     def test_preexec(self):
         # DISCLAIMER: Setting environment variables is *not* a good use
         # of a preexec_fn.  This is merely a test.
diff --git a/Misc/NEWS.d/next/Library/2026-07-22-12-00-00.gh-issue-153970.Kq7Wn3.rst b/Misc/NEWS.d/next/Library/2026-07-22-12-00-00.gh-issue-153970.Kq7Wn3.rst
new file mode 100644
index 000000000000000..def943b46b59420
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-22-12-00-00.gh-issue-153970.Kq7Wn3.rst
@@ -0,0 +1,3 @@
+Calling :func:`str` on a :exc:`subprocess.CalledProcessError` no longer
+raises :exc:`TypeError` when its :attr:`!returncode` is not an integer, such
+as ``None``.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.