Summary
In a Dev Container whose Python environment is a venv outside the workspace
(/opt/venv, baked into the image), the notebook kernel picker never offers
that interpreter. Depending on timing it offers either the system interpreter
(/usr/bin/python3, which has no marimo) or nothing at all — leaving
marimo sandbox as the only selectable kernel.
Selecting the correct interpreter through Python: Select Interpreter — where
it is listed and selectable — does not change the kernel list.
Environment
|
|
| vscode-marimo |
0.17.2 (linux-x64) |
| VS Code (server) |
1.135.0 stable, commit 08d4889f |
| ms-python.python |
2026.4.0 |
| ms-python.vscode-python-envs |
1.36.0 |
| Remote |
Dev Containers over Remote-SSH (compose-based) |
| Container OS |
Ubuntu 22.04.3 LTS |
| marimo (in the venv) |
0.24.0 |
Relevant settings (dev container machine settings):
The environment itself is a normal venv and is fully usable:
$ /opt/venv/bin/python -c "import sys, marimo; print(sys.prefix, marimo.__version__)"
/opt/venv 0.24.0
$ cat /opt/venv/pyvenv.cfg
home = /usr/bin
version_info = 3.10.12
include-system-site-packages = false
prompt = example
Note that /usr/bin/python3 and /opt/venv/bin/python report the same
version (3.10.12); only the latter has marimo, pandas, etc.
Steps to reproduce
- Build a container image whose Python environment is a venv outside the
workspace folder (here /opt/venv; the workspace has no .venv).
- Point the Python extensions at it with the three settings above.
- Open a
.py marimo notebook and open the kernel picker.
Expected
A controller for /opt/venv/bin/python is offered (it is a known environment —
see below), so the notebook can run in the image's environment.
Actual
| Action |
Kernel list |
| After container rebuild |
Python 3.10.12 (= /usr/bin/python3, no marimo) + marimo sandbox |
After Developer: Reload Window |
marimo sandbox only |
After Python: Select Interpreter → /opt/venv/bin/python |
unchanged |
Developer: Restart Extension Host reconnects the remote and reproduces the
same race, so it is not a workaround.
The environment is definitely discovered by the Python tooling. From
Python Environments.log:
Discovered env: /opt/venv/bin/python
Discovered env: /usr/bin/python
Found venv environment: example (3.10.12)
Environment discovery complete: 2 environments found (Global: 1, venv: 1)
and it is persisted in the locator cache:
{"environment": {"executable": "/opt/venv/bin/python", "prefix": "/opt/venv",
"version": "3.10.12.final.0"}}
It is also listed and selectable in Python: Select Interpreter.
What the bundled code appears to do
Reading dist/extension.js of 0.17.2, NotebookControllers.refresh runs in
exactly two places:
const a = p.fn("NotebookControllers.refresh")(function* () {
const c = yield* r.knownEnvironments;
const l = c.filter(u => !rcn(u, { code: e, uvCacheDir: i }));
...
});
yield* a(); // once, at activation
yield* p.forkScoped(r.environmentChanges.pipe(V.runForEach(a))); // onDidChangeEnvironments
with
knownEnvironments: p.sync(() => e.environments.known)
Two consequences seem to follow:
-
The list is a snapshot taken at activation. The extension activates on
onStartupFinished (and declares extensionDependencies: ["ms-python.python"]),
which can be well before environment discovery finishes. In this container the
search path covers ~6.6 GB (/opt/nvidia 1.5 GB + /opt/venv 5.1 GB), and
discovery completes ~20 s after startup.
-
Selecting an interpreter does not refresh the controllers.
activeEnvironmentPathChanges is subscribed elsewhere (a debounced
PythonEnvInvalidation), but NotebookControllers.refresh is not wired to it,
so Python: Select Interpreter has no effect on the kernel list.
The filter itself is not the cause — rcn only excludes environments inside the
uv cache directory:
function rcn(t, e) {
if (C.isNone(e.uvCacheDir)) return !1;
try { return ncn(e.code.Uri.file(t.path).fsPath, e.uvCacheDir.value.fsPath) }
catch { return !1 }
}
/opt/venv is not under the uv cache, so it is not being filtered out — it seems
to simply be absent from the snapshot.
That the system interpreter appears after a rebuild but disappears after a reload
also suggests the contents of the snapshot depend on timing rather than on any
property of the environments.
Suggested fix
Re-run NotebookControllers.refresh when the active environment changes
(environments.onDidChangeActiveEnvironmentPath), in addition to
onDidChangeEnvironments. That alone would give users a reliable manual remedy —
pick the interpreter, get the kernel — regardless of how discovery is timed.
Optionally: await environments.refreshEnvironments() before the first refresh,
and/or expose a "marimo: Refresh kernels" command.
Two smaller things noticed while investigating
-
The kernel label can be misleading. The label is built as
name ? "<name> (Python <ver>)" : "Python <ver>" where name comes from
environment.name / the basename of environment.folderUri. An environment
resolved via python.defaultInterpreterPath has no environment, so it shows
as a bare Python <ver> — indistinguishable from the system interpreter when
both report the same version. (controller.description carries the path, which
helps, but it was not visible in the picker in our case.)
-
The sandbox kernel rewrites the notebook file. Selecting marimo sandbox
makes marimo insert PEP 723 metadata into the notebook
(marimo/_cli/sandbox.py::_ensure_marimo_in_script_metadata runs
uv add --script <file> marimo). For projects whose dependencies are locked
elsewhere this is an unwanted edit, and it re-appears on every run — it is not
obvious from the UI that choosing a kernel will modify the file on disk. A
confirmation, or a setting to disable it, would help.
(marimo.disableUvIntegration: true does not prevent this.)
Workaround we settled on
Run the notebooks outside the extension:
marimo edit --headless --host 127.0.0.1 --port 2718 notebooks/
marimo export html -o out.html notebooks/reports/<name>.py
python notebooks/reports/<name>.py
All three work correctly in the same container, with the same interpreter.
Summary
In a Dev Container whose Python environment is a venv outside the workspace
(
/opt/venv, baked into the image), the notebook kernel picker never offersthat interpreter. Depending on timing it offers either the system interpreter
(
/usr/bin/python3, which has no marimo) or nothing at all — leavingmarimo sandboxas the only selectable kernel.Selecting the correct interpreter through Python: Select Interpreter — where
it is listed and selectable — does not change the kernel list.
Environment
Relevant settings (dev container machine settings):
The environment itself is a normal venv and is fully usable:
Note that
/usr/bin/python3and/opt/venv/bin/pythonreport the sameversion (3.10.12); only the latter has marimo, pandas, etc.
Steps to reproduce
workspace folder (here
/opt/venv; the workspace has no.venv)..pymarimo notebook and open the kernel picker.Expected
A controller for
/opt/venv/bin/pythonis offered (it is a known environment —see below), so the notebook can run in the image's environment.
Actual
Python 3.10.12(=/usr/bin/python3, no marimo) +marimo sandboxDeveloper: Reload Windowmarimo sandboxonlyPython: Select Interpreter→/opt/venv/bin/pythonDeveloper: Restart Extension Hostreconnects the remote and reproduces thesame race, so it is not a workaround.
The environment is definitely discovered by the Python tooling. From
Python Environments.log:and it is persisted in the locator cache:
{"environment": {"executable": "/opt/venv/bin/python", "prefix": "/opt/venv", "version": "3.10.12.final.0"}}It is also listed and selectable in Python: Select Interpreter.
What the bundled code appears to do
Reading
dist/extension.jsof 0.17.2,NotebookControllers.refreshruns inexactly two places:
with
Two consequences seem to follow:
The list is a snapshot taken at activation. The extension activates on
onStartupFinished(and declaresextensionDependencies: ["ms-python.python"]),which can be well before environment discovery finishes. In this container the
search path covers ~6.6 GB (
/opt/nvidia1.5 GB +/opt/venv5.1 GB), anddiscovery completes ~20 s after startup.
Selecting an interpreter does not refresh the controllers.
activeEnvironmentPathChangesis subscribed elsewhere (a debouncedPythonEnvInvalidation), butNotebookControllers.refreshis not wired to it,so
Python: Select Interpreterhas no effect on the kernel list.The filter itself is not the cause —
rcnonly excludes environments inside theuv cache directory:
/opt/venvis not under the uv cache, so it is not being filtered out — it seemsto simply be absent from the snapshot.
That the system interpreter appears after a rebuild but disappears after a reload
also suggests the contents of the snapshot depend on timing rather than on any
property of the environments.
Suggested fix
Re-run
NotebookControllers.refreshwhen the active environment changes(
environments.onDidChangeActiveEnvironmentPath), in addition toonDidChangeEnvironments. That alone would give users a reliable manual remedy —pick the interpreter, get the kernel — regardless of how discovery is timed.
Optionally: await
environments.refreshEnvironments()before the first refresh,and/or expose a "marimo: Refresh kernels" command.
Two smaller things noticed while investigating
The kernel label can be misleading. The label is built as
name ? "<name> (Python <ver>)" : "Python <ver>"wherenamecomes fromenvironment.name/ the basename ofenvironment.folderUri. An environmentresolved via
python.defaultInterpreterPathhas noenvironment, so it showsas a bare
Python <ver>— indistinguishable from the system interpreter whenboth report the same version. (
controller.descriptioncarries the path, whichhelps, but it was not visible in the picker in our case.)
The sandbox kernel rewrites the notebook file. Selecting
marimo sandboxmakes marimo insert PEP 723 metadata into the notebook
(
marimo/_cli/sandbox.py::_ensure_marimo_in_script_metadatarunsuv add --script <file> marimo). For projects whose dependencies are lockedelsewhere this is an unwanted edit, and it re-appears on every run — it is not
obvious from the UI that choosing a kernel will modify the file on disk. A
confirmation, or a setting to disable it, would help.
(
marimo.disableUvIntegration: truedoes not prevent this.)Workaround we settled on
Run the notebooks outside the extension:
All three work correctly in the same container, with the same interpreter.