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.
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
PyodideWorker.jsand English copydeck currently return HTTP 200, indicating an intermittent load failure rather than a missing deployment asset.Technical findings
PyodideRunner.jsxcreates a blob worker whose bootstrap calls:importScripts("${process.env.PUBLIC_URL}/PyodideWorker.js")The Sentry frame at
blob:...:4:18maps to that call. If the script cannot load, the worker has noerrorormessageerrorhandler, leaving the runtime unavailable and producing an unhandled exception.The latest event also contains two rejected copydeck loads. Both
PyodideRunnerandSkulptRunnercall the asynchronousloadCopydeckFor()inside synchronoustry/catchblocks 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
errorandmessageerrorevents.loadCopydeckFor()rejection explicitly and keep ordinary Python errors usable without the friendly copydeck.Acceptance criteria
PyodideWorker.jsload does not produce an unhandled exception.Priority
Medium/normal hardening work; not a release blocker unless occurrence volume increases.