Change:
} function handleRunRequest(data: PythonRunEvent['data']) { <del> if (ignoreRunMessages) return; const code = (data.code.contents || '').slice(); // TODO: use reset-terminal for clarity? postMessage({ type: 'reset' }); const { runPython, getResetId, globals, printException } = initRunPython(); // use pyodide.runPythonAsync if we want top-level await </del> try { <del> runPython(code); } catch (e) { const err = e as PythonError; // the formatted exception is printed to the terminal printException(); // but the full error is logged to the console for debugging console.error(err); const resetId = getResetId(); // TODO: if a user raises a KeyboardInterrupt with a custom message this // will be treated as a reset, the client will resend their code and this // will loop. Can we fix that? Perhaps by using a custom exception? if (err.type === 'KeyboardInterrupt' && resetId) { // If the client sends a lot of run messages, it's easy for them to build // up while the worker is busy. As such, we both ignore any queued run // messages... ignoreRunMessages = true; // ...and tell the client that we're ignoring them. postMessage({ type: 'stopped', text: getResetId() }); </del> <ins> if (ignoreRunMessages) return; const code = (data.code.contents || '').slice(); // TODO: use reset-terminal for clarity? postMessage({ type: 'reset' }); const { runPython, getResetId, globals, printException } = initRunPython(); // use pyodide.runPythonAsync if we want top-level await try { runPython(code); } catch (e) { const err = e as PythonError; // the formatted exception is printed to the terminal printException(); // but the full error is logged to the console for debugging console.error(err); const resetId = getResetId(); // TODO: if a user raises a KeyboardInterrupt with a custom message this // will be treated as a reset, the client will resend their code and this // will loop. Can we fix that? Perhaps by using a custom exception? if (err.type === 'KeyboardInterrupt' && resetId) { // If the client sends a lot of run messages, it's easy for them to build // up while the worker is busy. As such, we both ignore any queued run // messages... ignoreRunMessages = true; // ...and tell the client that we're ignoring them. postMessage({ type: 'stopped', text: getResetId() }); } } finally { getResetId.destroy(); printException.destroy(); globals.destroy(); </ins> } <del> } finally { getResetId.destroy(); printException.destroy(); globals.destroy(); </del> <ins> } catch (e) { // This should only be reach if pyodide crashes, but it's helpful to log // the error in case it's something else. console.error(e); void resetPyodide(); </ins> } }

Discussion:
Pyodide can crash when adding or method to the class, while NOT trying to write invalid code. I'm mentioning intent, because it can be encountered when attempting to write correct code. ing instance of the class. or method to the class. On each key pressed, code will execute, including the newly written method. So when we are writing the method: Then starting writing inside of the brackets: . Code will be execute also when it's . That's when it will crash hard: Further changes to code will bring up in browser's console: With example challenge to the at the bottom of code. to class definition, add boilerplate: writing between brackets: on the right crashes at the point when it's . Ideally omitting the crashing part. After the crash window needs to be reloaded to make pyodide on the right work again. Tests still appear to work correctly. Recursive function going into maximum call stack size exceeded does not crash pyodide. I've tested example class from above on , it does not crash pyodide.