diff --git a/folder_paths.py b/folder_paths.py index 7304e1b7399d..efa75756fd1f 100644 --- a/folder_paths.py +++ b/folder_paths.py @@ -412,7 +412,12 @@ def cached_filename_list_(folder_name: str) -> tuple[list[str], dict[str, float] for x in out[1]: time_modified = out[1][x] folder = x - if os.path.getmtime(folder) != time_modified: + try: + if os.path.getmtime(folder) != time_modified: + return None + except OSError: + # A tracked folder was deleted or became inaccessible; treat the + # cache as stale so it gets rebuilt instead of raising. return None folders = folder_names_and_paths[folder_name] diff --git a/tests-unit/folder_paths_test/cache_invalidation_test.py b/tests-unit/folder_paths_test/cache_invalidation_test.py new file mode 100644 index 000000000000..0d0514897bb6 --- /dev/null +++ b/tests-unit/folder_paths_test/cache_invalidation_test.py @@ -0,0 +1,46 @@ +import os +import shutil +import tempfile + +import pytest + +import folder_paths + + +@pytest.fixture +def model_folder(): + """Register a temporary model category with a tracked subfolder.""" + folder_name = "cache_invalidation_test_cat" + with tempfile.TemporaryDirectory() as base: + category = os.path.join(base, "category") + subfolder = os.path.join(category, "sub") + os.makedirs(subfolder) + open(os.path.join(category, "a.safetensors"), "w").close() + open(os.path.join(subfolder, "b.safetensors"), "w").close() + + folder_paths.folder_names_and_paths[folder_name] = ( + [category], + {".safetensors"}, + ) + try: + yield folder_name, category, subfolder + finally: + folder_paths.folder_names_and_paths.pop(folder_name, None) + folder_paths.filename_list_cache.pop(folder_name, None) + folder_paths.cache_helper.clear() + + +def test_rebuilds_when_tracked_subfolder_deleted(model_folder): + folder_name, _category, subfolder = model_folder + + # Populate the filename cache, which records the mtime of every subfolder. + initial = folder_paths.get_filename_list(folder_name) + assert sorted(initial) == ["a.safetensors", "sub/b.safetensors"] + + # Remove a tracked subfolder at runtime (e.g. user deletes a model folder). + shutil.rmtree(subfolder) + + # The cache must be treated as stale and rebuilt, not crash with + # FileNotFoundError when probing the deleted folder's mtime. + refreshed = folder_paths.get_filename_list(folder_name) + assert refreshed == ["a.safetensors"]