diff --git a/marimo/_server/files/directory_scanner.py b/marimo/_server/files/directory_scanner.py index 447cc05d6d1..d8c60d45c13 100644 --- a/marimo/_server/files/directory_scanner.py +++ b/marimo/_server/files/directory_scanner.py @@ -188,6 +188,11 @@ def recurse(directory: str, depth: int = 0) -> list[FileInfo] | None: folders: list[FileInfo] = [] for entry in entries: + # Check the limit here, not just after adding a file: a + # sibling directory's recursion may have reached it. + if file_count[0] >= self.max_files: + break + # Skip hidden files and directories if entry.name.startswith("."): continue @@ -238,9 +243,6 @@ def recurse(directory: str, depth: int = 0) -> list[FileInfo] | None: files.append(file_info) # Also add to partial results for timeout recovery self.partial_results.append(file_info) - # Check if we've reached the limit - if file_count[0] >= self.max_files: - break except OSError as e: LOGGER.debug( "Error processing entry %s: %s", entry.path, e diff --git a/tests/_server/test_directory_scanner.py b/tests/_server/test_directory_scanner.py index c761d3f47cf..c0a9d4b28cb 100644 --- a/tests/_server/test_directory_scanner.py +++ b/tests/_server/test_directory_scanner.py @@ -118,6 +118,31 @@ def test_max_files_limit(self, test_dir: Path): files = DirectoryScanner(str(test_dir), max_files=5).scan() assert _count_files(files) == 5 + def test_max_files_limit_recursion_at_boundary( + self, test_dir: Path, monkeypatch: pytest.MonkeyPatch + ): + """A subdir recursion that reaches max_files must not let another + top-level file slip in. Forces the ordering that only flaked in CI.""" + import marimo._server.files.directory_scanner as scanner_mod + + for i in range(10): + _write(test_dir / f"app{i + 3}.py", MARIMO_APP) + + real_scandir = os.scandir + + def ordered_scandir(path: str): # type: ignore[no-untyped-def] + entries = list(real_scandir(path)) + dirs = [e for e in entries if e.is_dir()] + files = sorted( + (e for e in entries if not e.is_dir()), key=lambda e: e.name + ) + # 4 files, then the nested dir (reaches the limit), then the rest. + return files[:4] + dirs + files[4:] + + monkeypatch.setattr(scanner_mod.os, "scandir", ordered_scandir) + files = DirectoryScanner(str(test_dir), max_files=5).scan() + assert _count_files(files) == 5 + def test_skip_common_directories(self, test_dir: Path): skip_names = ( "venv",