From 31a9076123fa4818e4ab152ecc07157ea0a8a374 Mon Sep 17 00:00:00 2001 From: Myles Scolnick Date: Mon, 13 Jul 2026 14:45:50 -0400 Subject: [PATCH 1/2] fix(code_mode): skip missing-package nudge for marimo.* imports A failed `import marimo.` surfaces as a missing `marimo` package, prompting code_mode to install marimo. But marimo is always installed and a bad submodule import can't be fixed by installing it, so filter marimo-owned modules/packages out of the missing-packages computation. --- marimo/_runtime/callbacks/packages.py | 24 +++++++++++++++++++++++- tests/_runtime/test_runtime.py | 22 ++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/marimo/_runtime/callbacks/packages.py b/marimo/_runtime/callbacks/packages.py index dae5416f2ef..e12752f9b47 100644 --- a/marimo/_runtime/callbacks/packages.py +++ b/marimo/_runtime/callbacks/packages.py @@ -37,6 +37,17 @@ LOGGER = _loggers.marimo_logger() +def _is_marimo_module(module_name: str) -> bool: + """True for marimo itself or any of its submodules. + + A failed `import marimo.` surfaces as a missing "marimo" + package, but marimo is always installed and the error can never be + fixed by installing it. Treating it as missing would nudge callers + (e.g. code_mode) to install marimo, so we always skip these. + """ + return module_name == "marimo" or module_name.startswith("marimo.") + + class PackagesCallbacks: def __init__(self, kernel: Kernel): self._kernel = kernel @@ -69,7 +80,8 @@ def send_missing_packages_alert(self, missing_packages: set[str]) -> None: packages = sorted( pkg for mod in missing_packages - if not self.package_manager.attempted_to_install( + if not _is_marimo_module(mod) + and not self.package_manager.attempted_to_install( pkg := self.package_manager.module_to_package(mod) ) ) @@ -137,12 +149,22 @@ def missing_packages_hook( # Convert modules to packages for mod in missing_modules: + # marimo is always installed; a failed `import marimo.` can't + # be fixed by installing marimo, so never report it as missing. + if _is_marimo_module(mod): + continue pkg = self.package_manager.module_to_package(mod) # filter out packages that we already attempted to install # to prevent an infinite loop if not self.package_manager.attempted_to_install(pkg): missing_packages.add(pkg) + # Drop marimo itself if it slipped in via a package-name path + # (e.g. ManyModulesNotFoundError or a pip-install suggestion). + missing_packages = { + pkg for pkg in missing_packages if not _is_marimo_module(pkg) + } + if not missing_packages: return diff --git a/tests/_runtime/test_runtime.py b/tests/_runtime/test_runtime.py index e01f56d0df8..1c392e0dd2c 100644 --- a/tests/_runtime/test_runtime.py +++ b/tests/_runtime/test_runtime.py @@ -2694,6 +2694,28 @@ def _repr_html_(self): op_names = [op.get("op") for op in stream.operations] assert "missing-package-alert" in op_names + async def test_marimo_submodule_not_reported_as_missing( + self, mocked_kernel: MockedKernel, exec_req: ExecReqProvider + ) -> None: + """A failed `import marimo.` must not raise a missing-package alert. + + marimo is always installed; a missing submodule can't be fixed by + installing marimo, so we never nudge callers (e.g. code_mode) to + install it. + """ + k = mocked_kernel.k + assert k.packages_callbacks.package_manager is not None + + await k.run( + [ + exec_req.get("import marimo.this_submodule_does_not_exist"), + ] + ) + + stream = MockStream(mocked_kernel.stream) + op_names = [op.get("op") for op in stream.operations] + assert "missing-package-alert" not in op_names + class TestDisable: async def test_disable_and_reenable_not_stale( From 3bb3bb1b47a7774d51b99e8c19ab58876f2656a4 Mon Sep 17 00:00:00 2001 From: Myles Scolnick Date: Mon, 13 Jul 2026 15:35:35 -0400 Subject: [PATCH 2/2] test(save): make test_decorator_in_kernel order-independent The `@mo.cache` functions in `transitive_imports` bind their caching context the first time the module is imported. When that first import happened inside the kernel setup cell, they bound to the kernel's globals (my_module = module_0) instead of their own module scope (my_module = module_1), so the pinned hash collided with the kernel-defined function and the test failed. The test previously relied on a sibling test importing the module outside kernel execution first; under pytest-xdist that ordering isn't guaranteed, so the test flaked in CI. Import the module explicitly before running the kernel, matching the sibling tests' pattern. --- tests/_save/test_external_decorators.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/_save/test_external_decorators.py b/tests/_save/test_external_decorators.py index 94df3995f4d..82eaee1043c 100644 --- a/tests/_save/test_external_decorators.py +++ b/tests/_save/test_external_decorators.py @@ -118,6 +118,20 @@ def has_dep_with_differing_name_works_pinned() -> tuple[int]: async def test_decorator_in_kernel( lazy_kernel: Kernel, exec_req: ExecReqProvider ) -> None: + # The `@mo.cache` functions in `transitive_imports` bind their + # caching context the first time the module is imported. If that + # first import happens inside a kernel cell (as it would below), + # they bind to the kernel's globals instead of their own module + # scope, corrupting their pinned hash. Import the module here — + # outside any cell execution — so the external binding is correct + # regardless of test execution order (e.g. under pytest-xdist, + # where this test may run before its siblings that would + # otherwise prime this state). + for module in list(sys.modules.keys()): + if module.startswith("tests._save.external_decorators"): + del sys.modules[module] + import tests._save.external_decorators.transitive_imports # noqa: F401 + k = lazy_kernel await k.run( [