https://github.com/python/cpython/commit/a7b2e1f4e07718e1e047dd7683b488e239688ceb
commit: a7b2e1f4e07718e1e047dd7683b488e239688ceb
branch: main
author: Sreehari Annam <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-08-14T20:43:19+05:30
summary:

gh-135736: call exception handler for all BaseExceptions in asyncio.TaskGroup 
(#154538)

files:
A Misc/NEWS.d/next/Library/2026-07-23-12-27-52.gh-issue-135736.hkuGvu.rst
M Lib/asyncio/taskgroups.py
M Lib/test/test_asyncio/test_taskgroups.py

diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py
index a431b45a19489d5..1b77d6d317e9375 100644
--- a/Lib/asyncio/taskgroups.py
+++ b/Lib/asyncio/taskgroups.py
@@ -140,6 +140,18 @@ async def _aexit(self, et, exc):
         assert not self._tasks
 
         if self._base_error is not None:
+            # self._base_error (SystemExit or KeyboardInterrupt) is about
+            # to propagate out of this method, which discards any other
+            # collected task errors silently.  Report them instead of
+            # losing them.  See gh-135736.
+            for suppressed_exc in self._errors:
+                self._loop.call_exception_handler({
+                    'message': 'TaskGroup task exception was not '
+                               'propagated because the TaskGroup body '
+                               'is being closed with a BaseException',
+                    'exception': suppressed_exc,
+                    'task_group': self,
+                })
             try:
                 raise self._base_error
             finally:
diff --git a/Lib/test/test_asyncio/test_taskgroups.py 
b/Lib/test/test_asyncio/test_taskgroups.py
index bc246400b83e9b2..f5b7daf9e2ce1d0 100644
--- a/Lib/test/test_asyncio/test_taskgroups.py
+++ b/Lib/test/test_asyncio/test_taskgroups.py
@@ -608,6 +608,39 @@ async def runner():
             get_error_types(cm.exception), {MyBaseExc, ZeroDivisionError}
         )
 
+    async def test_taskgroup_20b(self):
+        # Same setup as test_taskgroup_20 (a KeyboardInterrupt from the
+        # "async with" body itself, alongside a sibling task's exception):
+        # raising self._base_error out of _aexit() discards self._errors
+        # silently. The sibling's exception can't be raised alongside
+        # the KeyboardInterrupt (only one exception can propagate), but
+        # it must be reported via the loop's exception handler instead
+        # of being discarded silently. See gh-135736.
+        async def crash_soon():
+            await asyncio.sleep(0.1)
+            1 / 0
+
+        async def nested():
+            try:
+                await asyncio.sleep(10)
+            finally:
+                raise KeyboardInterrupt
+
+        async def runner():
+            async with taskgroups.TaskGroup() as g:
+                g.create_task(crash_soon())
+                await nested()
+
+        contexts = []
+        loop = asyncio.get_running_loop()
+        loop.set_exception_handler(lambda loop, context: 
contexts.append(context))
+
+        with self.assertRaises(KeyboardInterrupt):
+            await runner()
+
+        self.assertEqual(len(contexts), 1)
+        self.assertIsInstance(contexts[0]['exception'], ZeroDivisionError)
+
     async def _test_taskgroup_21(self):
         # This test doesn't work as asyncio, currently, doesn't
         # correctly propagate KeyboardInterrupt (or SystemExit) --
diff --git 
a/Misc/NEWS.d/next/Library/2026-07-23-12-27-52.gh-issue-135736.hkuGvu.rst 
b/Misc/NEWS.d/next/Library/2026-07-23-12-27-52.gh-issue-135736.hkuGvu.rst
new file mode 100644
index 000000000000000..76a8b7105f75850
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-23-12-27-52.gh-issue-135736.hkuGvu.rst
@@ -0,0 +1,5 @@
+Fix :class:`asyncio.TaskGroup` silently discarding errors from sibling
+tasks whenever the ``async with`` block exits with a :exc:`SystemExit` or
+:exc:`KeyboardInterrupt`. These errors are now reported via
+:meth:`loop.call_exception_handler() <asyncio.loop.call_exception_handler>`
+instead of being lost.

_______________________________________________
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]

Reply via email to