Skip to content

asyncio: default exception handler does not run in the task's context #159303

Description

@Reskov

Bug report

gh-96704 (PR #96756, 3.12) changed BaseEventLoop.call_exception_handler() to run the handler inside task.get_context() / handle.get_context(). The change was applied only to the branch where a custom handler is set with loop.set_exception_handler(). When no handler is set, default_exception_handler() is called directly, in whatever context is current at that moment.

if self._exception_handler is None:
try:
self.default_exception_handler(context)
except (SystemExit, KeyboardInterrupt):
raise
except BaseException:
# Second protection layer for unexpected errors
# in the default implementation, as well as for subclassed
# event loops with overloaded "default_exception_handler".
logger.error('Exception in default exception handler',
exc_info=True)

import asyncio
import contextvars
import logging
import sys

request_id = contextvars.ContextVar("request_id", default=None)


class RequestIdFilter(logging.Filter):
    def filter(self, record):
        record.request_id = request_id.get()
        return True


class NoTracebackFormatter(logging.Formatter):
    def formatException(self, exc_info):
        return ""


handler = logging.StreamHandler(sys.stdout)
handler.addFilter(RequestIdFilter())
handler.setFormatter(NoTracebackFormatter("  logged with request_id=%(request_id)s"))
logging.getLogger("asyncio").addHandler(handler)
logging.getLogger("asyncio").propagate = False


async def fail():
    request_id.set("req-1")
    raise ValueError


async def other_request():
    request_id.set("req-2")
    task = asyncio.ensure_future(fail())
    await asyncio.sleep(0.01)
    del task  # "Task exception was never retrieved" is reported here


async def main(custom_handler):
    if custom_handler:
        asyncio.get_running_loop().set_exception_handler(
            lambda loop, context: loop.default_exception_handler(context)
        )
    await asyncio.create_task(other_request())


print(sys.version)
print("default handler (expected req-1):")
asyncio.run(main(custom_handler=False))
print("custom handler calling default_exception_handler (expected req-1):")
asyncio.run(main(custom_handler=True))

Output (identical on 3.12.7, 3.13.16, 3.14.8, 3.15.0):

default handler (expected req-1):
  logged with request_id=req-2
custom handler calling default_exception_handler (expected req-1):
  logged with request_id=req-1

Expected: the default handler runs in the failed task's (or handle's) context, as custom handlers have since gh-96704.

Workaround: loop.set_exception_handler(lambda loop, context: loop.default_exception_handler(context))

CPython versions tested on:

3.15

Operating systems tested on:

macOS

Linked PRs

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    on Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions