Fix interactive workflow execution and display matplotlib figures inline - #76
Conversation
websockets >= 14 renamed extra_headers to additional_headers and made the new asyncio implementation the default, so workflow execution failed with 'create_connection() got an unexpected keyword argument extra_headers'. Import connect from websockets.asyncio.client (available since 13.0) so the code works with both the locked 13.1 and newer versions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L77mAJQZVU9rC2BecmBqbn
The interactive run dropped all image output: display_data messages had no handler, figures over the 8 MB WebSocket frame limit aborted the run, and the stream stopped at execute_reply, discarding iopub messages still in flight. Forward image/png payloads as a new 'image' SSE event, raise max_size to 128 MB, and finish only after both execute_reply and the iopub idle status have arrived. On the frontend, keep the SSE event type across read() chunks (a multi-MB data: line spans many chunks) and render image events as inline images in the execution log modal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L77mAJQZVU9rC2BecmBqbn
The generated script ends with sys.exit(main()), which raises SystemExit inside the Jupyter kernel and marks successful runs as errors. Apply the same replacement the notebook export already uses before sending the code to the kernel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L77mAJQZVU9rC2BecmBqbn
There was a problem hiding this comment.
Pull request overview
This PR repairs end-to-end interactive workflow execution (Run button) by fixing the Jupyter kernel WebSocket connection, ensuring execution completion waits for both shell and iopub idle signals, and extending the SSE/GUI pipeline to render inline matplotlib figures in the execution log.
Changes:
- Update kernel WS connection behavior (headers API + frame size) and improve completion logic to avoid dropping late iopub output.
- Forward Jupyter
display_data/execute_resultimage/pngpayloads as a newimageSSE event. - Improve frontend SSE parsing across read chunks and render image events inline in the log modal.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| gui/workflow_frontend/src/views/home/components/projectSelector.tsx | Handles new image SSE event by appending an image log entry. |
| gui/workflow_frontend/src/views/home/components/logViewModal.tsx | Extends log entry types and renders image entries as inline images. |
| gui/workflow_frontend/src/api/workflowRunApi.ts | Fixes SSE parsing to retain event: type across chunk boundaries for large data: payloads. |
| gui/workflow_backend/django-project/app/workflow/views.py | Replaces sys.exit(main()) to avoid SystemExit within the Jupyter kernel execution path. |
| gui/workflow_backend/django-project/app/workflow/jupyter_execution_service.py | Updates WS connect API usage, raises frame limits, forwards inline images, and waits for both execute_reply and iopub idle. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import httpx | ||
| import websockets | ||
| from websockets.asyncio.client import connect | ||
|
|
There was a problem hiding this comment.
Verified empirically against both versions — the current import is correct, and the suggested change would reintroduce the exact bug this PR fixes:
- websockets 13.1 (poetry.lock):
websockets.asyncio.client.connectexists (the new asyncio implementation shipped in 13.0) and acceptsadditional_headers. The legacywebsockets.client.connectonly acceptsextra_headersthere, sofrom websockets.client import connectwithadditional_headers=would raise the samecreate_connection() got an unexpected keyword argumentTypeError. - websockets 16.0 (what the backend container actually runs):
websockets.client.connectis deprecated (emits a DeprecationWarning), whilewebsockets.asyncio.client.connectis the supported path.
# websockets==13.1
websockets.asyncio.client.connect exists
new API additional_headers: True
legacy websockets.client.connect additional_headers: False
legacy websockets.client.connect extra_headers: True
The change was also exercised end-to-end against a live kernel in the backend container. Keeping the import as-is.
Summary
Interactive workflow execution (the Run button) failed or silently lost output in several ways. This PR makes it work end-to-end and renders matplotlib figures inline in the execution log.
Fixes
extra_headersAPI, so the kernel WebSocket connection always failed. Migrated towebsockets.asyncio.client.connectwithadditional_headers, which works on both the locked 13.1 and newer versions.display_datamessages (whatplt.show()emits) were dropped by the backend.image/pngpayloads are now forwarded as a newimageSSE event and rendered as images in the execution log modal.execute_replyand the iopubstatus: idlehave arrived. The channels are independent, soexecute_replycould beat large iopub messages still in flight, and everything behind them was discarded.data:line spanned multiple read chunks (guaranteed to happen for images).sys.exit(main()), which raisesSystemExitinside the Jupyter kernel. The run path now applies the samemain()replacement the notebook export already uses.Changed files
jupyter_execution_service.py(WS connection, message handling, completion condition),views.py(sys.exit replacement)workflowRunApi.ts(parser),projectSelector.tsx(image event),logViewModal.tsx(inline image rendering)Testing
tsc --noEmitpasses; isort clean (black flags only pre-existing lines left untouched).🤖 Generated with Claude Code
https://claude.ai/code/session_01L77mAJQZVU9rC2BecmBqbn