diff --git a/news/changelog-1.10.md b/news/changelog-1.10.md index ca5792b8116..88925d00794 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 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 - ([#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 4ac5383e004..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 @@ -563,12 +572,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 e.args == ("status",): + cell.outputs.append(nbformat.v4.new_output( + output_type="error", + ename="KernelProtocolError", + evalue="Kernel returned execute_reply without status field", + traceback=[], + )) + else: + raise cell.source = source # if lines_to_next_cell is 0 then fix it to be 1