Skip to content

fix: resolve 'no comm channel defined' error with plotly 6.x#220

Open
cpsievert wants to merge 2 commits intomainfrom
fix-plotly6-comm-close-error
Open

fix: resolve 'no comm channel defined' error with plotly 6.x#220
cpsievert wants to merge 2 commits intomainfrom
fix-plotly6-comm-close-error

Conversation

@cpsievert
Copy link
Collaborator

@cpsievert cpsievert commented Jan 22, 2026

Fixes posit-dev/py-shiny#2156
Closes #215
Closes #137

This PR fixes two issues with plotly 6.x and shinywidgets:

  1. "Syncing error: no comm channel defined" - Error during widget cleanup
  2. Widgets not visible with streaming patterns - Widgets destroyed prematurely when effects are created inside render functions

Problem 1: Comm Channel Error

Symptoms

  • Console shows: Error during model cleanup: Syncing error: no comm channel defined
  • Widgets may flicker or disappear during cleanup cycles

Root Cause

Plotly 6.0 switched FigureWidget to use anywidget instead of their previous implementation. This introduced an incompatibility with how ipywidgets handles model cleanup:

  1. ipywidgets' close() method deletes this.comm BEFORE removing views (widget.ts lines 232-243)

  2. When views are removed, they fire a 'remove' event that tries to sync _view_count back to the server (widget.ts lines 789-793):

    this.once('remove', () => {
      if (typeof this.model.get('_view_count') === 'number') {
        this.model.set('_view_count', this.model.get('_view_count') - 1);
        this.model.save_changes();  // Tries to sync!
      }
    });
  3. The sync fails because the comm was already deleted, throwing "Syncing error: no comm channel defined"

Solution

Set comm_live = false before calling m.close() - this prevents save_changes() from attempting to sync when views are removed.

Problem 2: Widgets Not Visible with Streaming Patterns

Symptoms

  • Widgets created with patterns like render_plotly_streaming (effects inside render functions) would not appear
  • The widget would be destroyed immediately after creation

Root Cause

Previously, widgets were cleaned up via ctx.on_invalidate() which fired whenever the render context was invalidated. When effects are created inside render functions (like in render_plotly_streaming), the effect's dependency changes would invalidate the context and destroy the widget prematurely - even though the render function itself wasn't re-running.

Solution

Changed widget cleanup to only close the old widget when a new widget is being rendered for the same output, rather than on every context invalidation. This properly supports patterns where effects update widgets in-place without re-creating them.

Key changes in _shinywidgets.py:

  • Added OUTPUT_WIDGET_MAP to track which widget is associated with each (session_id, output_id) pair
  • When a new widget is rendered, check if there's an existing widget for that output and close it
  • Removed the on_invalidate cleanup hook that was causing premature destruction

Test Plan

  • Run the model-score example: cd examples/model-score && shiny run app.py
  • Verify plotly widgets render and update via streaming without errors
  • Verify no "no comm channel defined" errors in the console

🤖 Generated with Claude Code

Fixes posit-dev/py-shiny#2156

Plotly 6.0 switched FigureWidget to use anywidget, which doesn't have
a destroy() method. When m.close() is called, ipywidgets deletes the
comm before removing views, and the view removal tries to sync _view_count,
causing "Syncing error: no comm channel defined".

The fix sets comm_live=false before calling m.close(), which prevents
save_changes() from attempting to sync during view removal. Also wraps
the close operations in try-catch to suppress cleanup errors that don't
affect functionality.

Note: The model-score streaming example still has visibility issues with
plotly 6.x, but this is a pre-existing problem (also present on main)
and should be investigated separately.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@cpsievert cpsievert force-pushed the fix-plotly6-comm-close-error branch from 71fca1b to e8859a6 Compare January 23, 2026 15:43
Previously, widgets were cleaned up via `ctx.on_invalidate()` which fired
whenever the render context was invalidated. This caused issues when effects
were created inside render functions (like in `render_plotly_streaming`) -
the effect's dependency changes would invalidate the context and destroy
the widget prematurely.

Now widgets are only closed when a NEW widget is rendered for the same
output, not on every context invalidation. This properly supports patterns
where effects update widgets in-place without re-creating them.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant