diff --git a/Doc/c-api/sys.rst b/Doc/c-api/sys.rst index ee73c1c8adaa7b..0a446f86e22eaf 100644 --- a/Doc/c-api/sys.rst +++ b/Doc/c-api/sys.rst @@ -433,7 +433,7 @@ accessible to C code. They all work with the current interpreter thread's This function is safe to call before :c:func:`Py_Initialize`. When called after runtime initialization, existing audit hooks are notified and may silently abort the operation by raising an error subclassed from - :class:`Exception` (other errors will not be silenced). + :class:`RuntimeError` (other errors will not be silenced). The hook function is always called with an :term:`attached thread state` by the Python interpreter that raised the event. @@ -447,7 +447,7 @@ accessible to C code. They all work with the current interpreter thread's If the interpreter is initialized, this function raises an auditing event ``sys.addaudithook`` with no arguments. If any existing hooks raise an - exception derived from :class:`Exception`, the new hook will not be + exception derived from :class:`RuntimeError`, the new hook will not be added and the exception is cleared. As a result, callers cannot assume that their hook has been added unless they control all existing hooks. @@ -462,6 +462,11 @@ accessible to C code. They all work with the current interpreter thread's .. versionadded:: 3.8 + .. versionchanged:: 3.8.1 + + Exceptions derived from :class:`Exception` but not :class:`RuntimeError` + are no longer suppressed. + .. _processcontrol: diff --git a/Lib/test/audit-tests.py b/Lib/test/audit-tests.py index 8be5bf8aa4f546..53bd369fabe21f 100644 --- a/Lib/test/audit-tests.py +++ b/Lib/test/audit-tests.py @@ -109,6 +109,16 @@ def test_block_add_hook_baseexception(): pass +def test_block_add_hook_valueerror(): + # Non-RuntimeError exceptions (like ValueError) should propagate out + with assertRaises(ValueError): + with TestHook( + raise_on_events="sys.addaudithook", exc_type=ValueError + ) as hook1: + with TestHook() as hook2: + pass + + def test_marshal(): import marshal o = ("a", "b", "c", 1, 2, 3) diff --git a/Lib/test/test_audit.py b/Lib/test/test_audit.py index db4e1eb9999c1f..e9e546fdbfd6fb 100644 --- a/Lib/test/test_audit.py +++ b/Lib/test/test_audit.py @@ -58,6 +58,9 @@ def test_block_add_hook(self): def test_block_add_hook_baseexception(self): self.do_test("test_block_add_hook_baseexception") + def test_block_add_hook_valueerror(self): + self.do_test("test_block_add_hook_valueerror") + def test_marshal(self): import_helper.import_module("marshal") diff --git a/Misc/NEWS.d/next/Library/2026-07-03-02-11-00.gh-issue-152912.Mv3KpR.rst b/Misc/NEWS.d/next/Library/2026-07-03-02-11-00.gh-issue-152912.Mv3KpR.rst new file mode 100644 index 00000000000000..a4d7c4a9b0a294 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-03-02-11-00.gh-issue-152912.Mv3KpR.rst @@ -0,0 +1 @@ +``sys.addaudithook()`` now correctly suppresses only :exc:`RuntimeError` instead of all :exc:`Exception` subclasses when an existing audit hook raises during hook registration. Patch by Yeongu Kim. diff --git a/Python/sysmodule.c b/Python/sysmodule.c index d9f7b9c449cfb9..3fe396fa440803 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -527,8 +527,8 @@ sys_addaudithook_impl(PyObject *module, PyObject *hook) /* Invoke existing audit hooks to allow them an opportunity to abort. */ if (_PySys_Audit(tstate, "sys.addaudithook", NULL) < 0) { - if (_PyErr_ExceptionMatches(tstate, PyExc_Exception)) { - /* We do not report errors derived from Exception */ + if (_PyErr_ExceptionMatches(tstate, PyExc_RuntimeError)) { + /* We do not report errors derived from RuntimeError */ _PyErr_Clear(tstate); Py_RETURN_NONE; }