From db1deb161752146b1431620b74357454891b02a5 Mon Sep 17 00:00:00 2001
From: Chris Dryden <117657238+chrisdrydenaltmetric@users.noreply.github.com>
Date: Mon, 1 Jun 2026 11:07:01 +0100
Subject: [PATCH] Merge pull request #33895 from
overleaf/cd-pyodide-unsupported-module-message
Improve error messaging for python modules unsupported by Pyodide
GitOrigin-RevId: 038c672ad9da46ea6d4640b8ed37426c92d22e72
---
.../editor/python/pyodide.worker.ts | 16 ++++++-
.../components/python-output-pane.spec.tsx | 42 +++++++++++++++++++
2 files changed, 57 insertions(+), 1 deletion(-)
diff --git a/services/web/frontend/js/features/ide-react/components/editor/python/pyodide.worker.ts b/services/web/frontend/js/features/ide-react/components/editor/python/pyodide.worker.ts
index 38d8ca577e..0c942b819e 100644
--- a/services/web/frontend/js/features/ide-react/components/editor/python/pyodide.worker.ts
+++ b/services/web/frontend/js/features/ide-react/components/editor/python/pyodide.worker.ts
@@ -31,6 +31,15 @@ function classifyErrorType(errorMessage: string): ExecutionErrorType {
return 'generic'
}
+function moduleNotFoundHelpMessage(): string {
+ return (
+ "Note: Only Pyodide's built-in packages are available in the browser. " +
+ 'Packages installed via pip cannot be used here. ' +
+ 'See https://pyodide.org/en/stable/usage/packages-in-pyodide.html ' +
+ 'for the list of supported packages.'
+ )
+}
+
function ensureDirectoryExists(fs: PyodideFS, filePath: string) {
const directory = path.dirname(filePath)
if (directory === '.' || directory === '/') {
@@ -221,7 +230,12 @@ async function handleRunCode(msg: RunCodeRequest) {
if (runError) {
const errorMessage =
runError instanceof Error ? runError.message : String(runError)
- postFailure('stderr', errorMessage, classifyErrorType(errorMessage))
+ const errorType = classifyErrorType(errorMessage)
+ const fullMessage =
+ errorType === 'ModuleNotFoundError'
+ ? `${errorMessage}\n${moduleNotFoundHelpMessage()}`
+ : errorMessage
+ postFailure('stderr', fullMessage, errorType)
return
}
diff --git a/services/web/test/frontend/features/ide-react/components/python-output-pane.spec.tsx b/services/web/test/frontend/features/ide-react/components/python-output-pane.spec.tsx
index 1e5d8d12df..87dbea0fe0 100644
--- a/services/web/test/frontend/features/ide-react/components/python-output-pane.spec.tsx
+++ b/services/web/test/frontend/features/ide-react/components/python-output-pane.spec.tsx
@@ -257,6 +257,9 @@ describe('', function () {
'have.class',
'ide-redesign-python-output-pane-line-stderr'
)
+ cy.findByText("Only Pyodide's built-in packages", { exact: false }).should(
+ 'not.exist'
+ )
})
it('renders the interrupt message as an info line', function () {
@@ -380,4 +383,43 @@ describe('', function () {
)
cy.findByText('hello from tomli').should('exist')
})
+
+ it('augments ModuleNotFoundError output with help text about supported Pyodide packages', function () {
+ const executablePythonFileContents =
+ 'import this_module_does_not_exist_in_pyodide\n'
+ const projectFiles = {
+ [pythonExecutableScript.filename]: executablePythonFileContents,
+ }
+ const ProjectProvider = makeProjectProvider(projectFiles)
+
+ cy.mount(
+ executablePythonFileContents,
+ },
+ currentDocumentId: pythonExecutableScript.file_id,
+ openDocName: pythonExecutableScript.filename,
+ },
+ }}
+ providers={{ FileTreePathProvider, ProjectProvider }}
+ >
+
+
+
+
+ )
+
+ cy.findByRole('button', { name: 'Run Python code' })
+ .should('not.be.disabled')
+ .click()
+
+ cy.findByText('ModuleNotFoundError', { exact: false }).should('exist')
+ cy.findByText("Only Pyodide's built-in packages", { exact: false }).should(
+ 'have.class',
+ 'ide-redesign-python-output-pane-line-stderr'
+ )
+ })
})