Skip to content

Handle Pyodide worker load failures and unhandled copydeck rejections #1616

Description

@sentry

Summary

Harden the Python runtime against transient production asset-loading failures reported by Sentry. These failures should produce a recoverable editor state rather than unhandled browser errors.

Sentry issue: EDITOR-UI-AC3

Evidence

  • 29 production events affecting 23 users over two months.
  • Five occurrences in the seven days up to 1 September 2026.
  • Every event is on Apple WebKit: 27 desktop Safari, one Mobile Safari, and one Chrome on iOS.
  • Events span multiple projects, URLs, locales, and users, so this is not one broken project.
  • The production v0.36.6 PyodideWorker.js and English copydeck currently return HTTP 200, indicating an intermittent load failure rather than a missing deployment asset.
  • The issue is low-volume and not an active outage, but it is recurring and user-affecting.

Technical findings

PyodideRunner.jsx creates a blob worker whose bootstrap calls:

importScripts("${process.env.PUBLIC_URL}/PyodideWorker.js")

The Sentry frame at blob:...:4:18 maps to that call. If the script cannot load, the worker has no error or messageerror handler, leaving the runtime unavailable and producing an unhandled exception.

The latest event also contains two rejected copydeck loads. Both PyodideRunner and SkulptRunner call the asynchronous loadCopydeckFor() inside synchronous try/catch blocks without awaiting or catching the returned Promise. A failed request therefore becomes an unhandled rejection, and both runners can request the same copydeck.

Sentry's generic network-error grouping may contain multiple transient failures, so worker and copydeck failures should be reported separately.

Proposed changes

  • Handle worker error and messageerror events.
  • Put the editor into a recoverable failure state with a clear retry action when Pyodide cannot load.
  • Handle loadCopydeckFor() rejection explicitly and keep ordinary Python errors usable without the friendly copydeck.
  • Load the copydeck once rather than independently from both runner components.
  • Capture handled failures with enough context, and separate fingerprints, to distinguish worker and copydeck failures in Sentry.

Acceptance criteria

  • A failed PyodideWorker.js load does not produce an unhandled exception.
  • The learner sees a useful error state and can retry loading the runtime.
  • A failed copydeck request does not produce an unhandled Promise rejection.
  • Copydeck failure does not prevent normal Python execution or original error output.
  • The copydeck is not fetched twice for the same locale during runner setup.
  • Unit tests cover worker-load failure, retry, copydeck rejection, and successful loading.
  • Existing successful Pyodide and Skulpt behaviour remains unchanged.

Priority

Medium/normal hardening work; not a release blocker unless occurrence volume increases.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions