From 9dd7a43d94b2aadb63c6bf300b8a3a1011b1a614 Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Fri, 8 May 2026 10:05:53 +0100 Subject: [PATCH 1/2] Add a test for the failing scenario --- tests/test_kernel.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/test_kernel.py b/tests/test_kernel.py index 5fc828435..db5debb28 100644 --- a/tests/test_kernel.py +++ b/tests/test_kernel.py @@ -58,6 +58,42 @@ def test_simple_print(): _check_master(kc, expected=True) +@pytest.mark.parametrize( + ("code", "expect_error_status"), + [ + ("1/0", True), + ( + """ + class Test: + def __repr__(self): + 1 / 0 + + Test() + """, + True, + ), + ( + """ + ip = get_ipython() + try: + 1 / 0 + except: + ip.showtraceback() + """, + False, + ), + ], + ids=["runtime-error", "display-formatting-error", "explicit-showtraceback-ok"], +) +def test_execute_reply_error_status(code, expect_error_status): + with kernel() as kc: + msg_id, reply = execute(kc=kc, code=code) + assemble_output(kc.get_iopub_msg, parent_msg_id=msg_id, raise_error=False) + + has_error_status = reply["status"] == "error" + assert has_error_status is expect_error_status, reply + + def collect_outputs(get_iopub_msg, parent_msg_id, timeout=5): """Collect outputs until we get an idle message From 2ee91a692960bab53a83d11a92315cbae0947dc0 Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Fri, 8 May 2026 10:20:41 +0100 Subject: [PATCH 2/2] Mark cell execution as failing when display formatter errors out --- ipykernel/ipkernel.py | 4 +++- ipykernel/zmqshell.py | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 95af43e5e..215129063 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -465,7 +465,9 @@ async def run_cell(*args, **kwargs): err = res.error_before_exec if res.error_before_exec is not None else res.error_in_exec - if res.success: + displayhook_error = getattr(shell, "_last_traceback_during_displayhook", False) + + if res.success and not displayhook_error: reply_content["status"] = "ok" else: reply_content["status"] = "error" diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 0833432bf..a92bf7e49 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -661,6 +661,7 @@ def ask_exit(self): def run_cell(self, *args, **kwargs): """Run a cell.""" self._last_traceback = None + self._last_traceback_during_displayhook = False return super().run_cell(*args, **kwargs) def _showtraceback(self, etype, evalue, stb): @@ -675,6 +676,10 @@ def _showtraceback(self, etype, evalue, stb): } dh = self.displayhook + if getattr(dh, "msg", None) is not None: + # Errors raised while formatting display output should mark the + # current execute_request as failed. + self._last_traceback_during_displayhook = True # Send exception info over pub socket for other clients than the caller # to pick up topic = None