From 331ddd6c29e1bd1aea94b96c3e1d31d2a8c823c5 Mon Sep 17 00:00:00 2001 From: Christopher Jefferson Date: Fri, 17 Apr 2026 12:48:05 +0800 Subject: [PATCH 1/3] Handle missing status field in execute_reply from XEUS-based kernels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit XEUS-based Jupyter kernels (such as the Maple 2025 kernel) omit the 'status' field from execute_reply messages when execution produces an error. This violates the Jupyter messaging protocol, which requires every execute_reply to include status. The missing field causes a KeyError that crashes notebook execution. This is a workaround for a kernel-side protocol violation — the right fix is in the kernel. But the workaround is low-risk: we catch a specific KeyError("'status'") around client.execute_cell() and record it as a cell error output rather than crashing. Kernels that conform to the protocol are unaffected. Tested against Maple 2025 (XEUS 2.3.1) with 18 Quarto documents containing executable Maple code blocks. --- src/resources/jupyter/notebook.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/resources/jupyter/notebook.py b/src/resources/jupyter/notebook.py index 4ac5383e004..e1e8d4753b0 100644 --- a/src/resources/jupyter/notebook.py +++ b/src/resources/jupyter/notebook.py @@ -563,12 +563,26 @@ def cell_execute(client, cell, index, execution_count, eval_default, store_histo # execute (w/o yaml options so that cell magics work) source = cell.source cell.source = nb_strip_yaml_options(client, cell.source) - cell = client.execute_cell( - cell=cell, - cell_index=index, - execution_count=execution_count, - store_history=store_history, - ) + try: + cell = client.execute_cell( + cell=cell, + cell_index=index, + execution_count=execution_count, + store_history=store_history, + ) + except KeyError as e: + # Some kernels (e.g. XEUS-based Maple) omit 'status' from + # execute_reply on error, violating the Jupyter protocol. + # Record the error in the cell outputs rather than crashing. + if str(e) == "'status'": + cell.outputs.append(nbformat.v4.new_output( + output_type="error", + ename="KernelProtocolError", + evalue="Kernel returned execute_reply without status field", + traceback=["Cell source:", source], + )) + else: + raise cell.source = source # if lines_to_next_cell is 0 then fix it to be 1 From 307d8cccf980bd68c60a42dea1437b8a33dc0636 Mon Sep 17 00:00:00 2001 From: christophe dervieux Date: Fri, 17 Apr 2026 14:17:17 +0100 Subject: [PATCH 2/3] Tighten XEUS status-field guard and extend to cleanup cell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit e.args tuple comparison survives any upstream change to KeyError message formatting, which str(e) equality would silently miss. The cell source in the synthetic traceback field was redundant with the rendered output — the source is already shown next to the error block — and inflated error output for large cells. Traceback entries are conventionally stack frames, not source dumps. The kernel-deps cleanup_cell execute_cell call has the same exposure: any XEUS-based Python kernel (e.g. xeus-python) would hit it via cleanup.py. Guarded symmetrically but non-fatally, since cleanup failures shouldn't abort the render. --- news/changelog-1.10.md | 6 ++++++ src/resources/jupyter/notebook.py | 19 ++++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/news/changelog-1.10.md b/news/changelog-1.10.md index ca5792b8116..9ef244bd7c5 100644 --- a/news/changelog-1.10.md +++ b/news/changelog-1.10.md @@ -48,6 +48,12 @@ All changes included in 1.10: - ([#14297](https://github.com/quarto-dev/quarto-cli/pull/14297)): Fix `quarto.utils.is_empty_node()` returning inverted results for text nodes (`Str`, `Code`, `RawInline`). +## Engines + +### Jupyter + +- ([#14374](https://github.com/quarto-dev/quarto-cli/pull/14374)): Avoid a crash when a XEUS-based kernel (e.g. Maple 2025) returns `execute_reply` without the required `status` field. The failing cell is recorded as an error instead of aborting the render. (author: @ChrisJefferson) + ## Other fixes and improvements - ([#6651](https://github.com/quarto-dev/quarto-cli/issues/6651)): Fix dart-sass compilation failing in enterprise environments where `.bat` files are blocked by group policy. diff --git a/src/resources/jupyter/notebook.py b/src/resources/jupyter/notebook.py index e1e8d4753b0..af71097e65a 100644 --- a/src/resources/jupyter/notebook.py +++ b/src/resources/jupyter/notebook.py @@ -380,9 +380,18 @@ def handle_meta_object(obj): if cleanup_cell: kernel_supports_daemonization = True nb.cells.append(cleanup_cell) - client.execute_cell( - cell=cleanup_cell, cell_index=len(client.nb.cells) - 1, store_history=False - ) + try: + client.execute_cell( + cell=cleanup_cell, cell_index=len(client.nb.cells) - 1, store_history=False + ) + except KeyError as e: + # Same XEUS-based protocol violation as in cell_execute. + # Cleanup failures are non-fatal: trace and continue so the + # render still completes (kernel deps just won't be collected). + if e.args == ("status",): + trace("cleanup cell failed with missing 'status' in execute_reply; kernel deps unavailable") + else: + raise nb.cells.pop() # record kernel deps after execution (picks up imports that occurred @@ -574,12 +583,12 @@ def cell_execute(client, cell, index, execution_count, eval_default, store_histo # Some kernels (e.g. XEUS-based Maple) omit 'status' from # execute_reply on error, violating the Jupyter protocol. # Record the error in the cell outputs rather than crashing. - if str(e) == "'status'": + if e.args == ("status",): cell.outputs.append(nbformat.v4.new_output( output_type="error", ename="KernelProtocolError", evalue="Kernel returned execute_reply without status field", - traceback=["Cell source:", source], + traceback=[], )) else: raise From 61ee3ad447ba842d9541af57d0cf0d4fd55ee60d Mon Sep 17 00:00:00 2001 From: christophe dervieux Date: Fri, 17 Apr 2026 14:40:03 +0100 Subject: [PATCH 3/3] Clarify changelog scope: kernel-specific, not XEUS-wide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit XEUS's create_error_reply / create_successful_reply helpers in xhelper.cpp always set status, and the mainstream XEUS-based kernels (xeus-python, xeus-r) go through those helpers — their execute_reply messages are conforming. The omission is specific to kernel authors who bypass the helpers or use an older XEUS. Maple 2025 is the only one currently known to do that. The previous wording implied every XEUS-based kernel was at risk, which would misdirect users of conforming kernels. --- news/changelog-1.10.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/changelog-1.10.md b/news/changelog-1.10.md index 9ef244bd7c5..88925d00794 100644 --- a/news/changelog-1.10.md +++ b/news/changelog-1.10.md @@ -52,7 +52,7 @@ All changes included in 1.10: ### Jupyter -- ([#14374](https://github.com/quarto-dev/quarto-cli/pull/14374)): Avoid a crash when a XEUS-based kernel (e.g. Maple 2025) returns `execute_reply` without the required `status` field. The failing cell is recorded as an error instead of aborting the render. (author: @ChrisJefferson) +- ([#14374](https://github.com/quarto-dev/quarto-cli/pull/14374)): Avoid a crash when a third-party Jupyter kernel (observed with Maple 2025, built on XEUS) returns `execute_reply` without the required `status` field. The failing cell is recorded as an error instead of aborting the render. (author: @ChrisJefferson) ## Other fixes and improvements