@@ -288,13 +288,17 @@ def _execute_with_nbconvert(notebook_path: Path) -> dict[str, Any]:
288288 if result .returncode == 0 :
289289 return {"success" : True , "output_path" : output_path }
290290 else :
291- error = result .stderr .strip ().split ('\n ' )[- 1 ] if result .stderr else "Unknown error"
292- return {"success" : False , "error" : error [:200 ]}
291+ # Keep the tail of stderr — usually the traceback ending with the
292+ # actual exception. 200 chars was too aggressive and obscured the
293+ # diagnosis of real bugs (e.g. injected-setup-cell syntax errors).
294+ stderr = (result .stderr or "" ).strip ()
295+ error = stderr [- 2000 :] if stderr else "Unknown error"
296+ return {"success" : False , "error" : error }
293297
294298 except subprocess .TimeoutExpired :
295299 return {"success" : False , "error" : "Timeout" }
296300 except Exception as e :
297- return {"success" : False , "error" : str (e )[:200 ]}
301+ return {"success" : False , "error" : str (e )[:2000 ]}
298302
299303
300304def _save_output (path : Path , data : dict ):
@@ -310,12 +314,17 @@ def _inject_svg_setup(notebook: dict) -> dict:
310314 This ensures all plots are generated as SVG regardless of
311315 what the notebook's original settings might be.
312316 """
317+ # nbformat allows source as either a single string or a list of strings;
318+ # when using a list, each element MUST keep its trailing newline, otherwise
319+ # the kernel reassembles the lines into one undelimited blob that fails to
320+ # parse (e.g. "import matplotlibmatplotlib.use('Agg')...").
321+ # A plain multi-line string sidesteps the gotcha entirely.
313322 setup_cell = {
314323 "cell_type" : "code" ,
315324 "execution_count" : None ,
316325 "metadata" : {"tags" : ["setup" , "hide" ]},
317326 "outputs" : [],
318- "source" : MATPLOTLIB_SVG_SETUP .strip (). split ( "\n " ) ,
327+ "source" : MATPLOTLIB_SVG_SETUP .strip () + "\n " ,
319328 }
320329
321330 # Create a copy with setup cell prepended
0 commit comments